Proof of concept - Vue Polling a workaround for AWS API Gateway limitation

This commit is contained in:
Dillon Ngo
2023-12-20 14:47:56 +08:00
parent 359c103d01
commit 18df1c0c7b
31 changed files with 979 additions and 42 deletions
@@ -0,0 +1,11 @@
<?php
namespace App\Classes\Exceptions;
use App\Classes\ValueObjects\Constants\HttpStatus;
final class JobResourceNotFoundException extends ServiceApiException {
public function __construct(?string $message = null) {
parent::__construct($message ?? 'Unable to find the requested resource', HttpStatus::RESOURCE_NOT_FOUND);
}
}
@@ -15,6 +15,8 @@ use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Illuminate\Support\Facades\DB;
use App\Classes\Exceptions\JobResourceNotFoundException;
use Illuminate\Support\Facades\Log;
abstract class AbstractControllerLogic
{
@@ -62,6 +64,19 @@ abstract class AbstractControllerLogic
return $response;
} catch (ErrorException|GeneralExceptions|TypeError $exception){
if ($exception instanceof JobResourceNotFoundException) {
Log::error(sprintf(
"Uncaught exception '%s' with message '%s' in %s:%d",
get_class($exception),
$exception->getMessage(),
$exception->getTrace()[0]['file'],
$exception->getTrace()[0]['line']
));
}
else{
Log::error($exception);
}
return (new ApiResponseObject($this->getNotificationTitle().' failed', $exception->getMessage(),
!in_array($exception->getCode(), [0, 42000]) ? $exception->getCode() : HttpStatus::SERVER_ERROR))->handler();
@@ -6,6 +6,8 @@ namespace App\Classes\General\Eloquent;
use App\Classes\Exceptions\ResourceNotFoundException;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use App\Classes\Exceptions\JobResourceNotFoundException;
abstract class AbstractFetchRecord extends AbstractGetRecord
{
@@ -26,12 +28,18 @@ abstract class AbstractFetchRecord extends AbstractGetRecord
* @return Model
* @throws ResourceNotFoundException
*/
public function getResults(Builder $query): Model {
public function getResults(Builder $query, array $param = []): Model {
if(!$query->exists()){
throw new ResourceNotFoundException('Unable to find any record based on the criteria provided');
$table = $query->getModel()->getTable();
if($table ==='job_results'){
throw new JobResourceNotFoundException('Unable to find any job based on the criteria provided');
}
else{
throw new ResourceNotFoundException('Unable to find any record based on the criteria provided');
}
}
return $query->first();
}
}
}
@@ -50,9 +50,9 @@ abstract class AbstractGetRecord
* @param array $filters
* @return mixed
*/
public function handler(array $filters){
public function handler(array $filters, array $params = []){
$this->filters = collect($filters);
return $this->getResults($this->applyFiltersToQuery());
return $this->getResults($this->applyFiltersToQuery(), $params);
}
@@ -65,6 +65,6 @@ abstract class AbstractGetRecord
* @param Builder $query
* @return mixed
*/
abstract function getResults(Builder $query);
abstract function getResults(Builder $query, array $params = []);
}
}
@@ -16,11 +16,10 @@ abstract class AbstractListRecord extends AbstractGetRecord
* @return mixed
* @throws MalformedRequestException
*/
public function execute(array $filters = []){
public function execute(array $filters = [], array $param = []){
try{
return $this->handler($filters);
return $this->handler($filters, $param);
} catch (QueryException $exception){
throw new MalformedRequestException('Unable to fetch the list of records due to unexpected error');
@@ -32,7 +31,7 @@ abstract class AbstractListRecord extends AbstractGetRecord
* @param Builder $query
* @return mixed
*/
public function getResults(Builder $query) {
public function getResults(Builder $query, array $param = []) {
$filters = $this->getDecorationFilters();
if($filters->has('order_by')){
@@ -44,8 +43,12 @@ abstract class AbstractListRecord extends AbstractGetRecord
}
//dd($query->toSql());
return $filters->has('per_page') ? $query->paginate($filters->get('per_page')) : $query->get();
if(!empty($param)){
return $filters->has('per_page') ? $query->paginate($filters->get('per_page'), ['*'], 'page', $param['page']) : $query->get(); //page data from query parameters e.g ?page=1
}
else{
return $filters->has('per_page') ? $query->paginate($filters->get('per_page')) : $query->get();
}
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class JobId implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->where('job_id', $value);
}
}
+9
View File
@@ -2,6 +2,7 @@
namespace App\Classes\General;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Log;
@@ -40,4 +41,12 @@ class Helper
}
}
}
/**
* @param ResourceCollection $collection
* @return array
*/
static function collectionResponse(ResourceCollection $collection){
return json_decode($collection->response()->getContent(), true);
}
}
+43
View File
@@ -0,0 +1,43 @@
<?php
namespace App\Classes\Jobs;
use App\Classes\Modules\PackingLists\Processors\ListPackingListsJobProcessor;
use App\Classes\Modules\Jobs\DataTransferObjects\ListGenericJobObject;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ListPackingListsJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/** @var ListGenericJobObject */
private $listGenericJobObject;
/**
* ListPackingListsJob constructor.
* @param ListGenericJobObject $listGenericJobObject
*/
public function __construct(ListGenericJobObject $listGenericJobObject)
{
$this->listGenericJobObject = $listGenericJobObject;
}
public function handle()
{
$rawPayload = $this->job->payload();
if(isset($rawPayload['data']['commandName'])){
$this->listGenericJobObject->setJobCommandName($rawPayload['data']['commandName']);
}
if(isset($rawPayload['data']['command'])){
$this->listGenericJobObject->setJobCommand($rawPayload['data']['command']);
}
$result = (App()->make(ListPackingListsJobProcessor::class))->execute($this->listGenericJobObject);
}
}
@@ -0,0 +1,53 @@
<?php
namespace App\Classes\Modules\Jobs\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Jobs\Services\FetchesJobResult;
use App\Http\Resources\JobResultResource;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class FetchJobResultLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => 'Retrieved Data',
'message' => 'You have successfully retrieved data'
];
}
/** @var FetchesJobResult */
private $fetchesJobResult;
/**
* FetchJobResultLogic constructor.
* @param FetchesJobResult $fetchesJobResult
*/
public function __construct(FetchesJobResult $fetchesJobResult)
{
$this->fetchesJobResult = $fetchesJobResult;
}
/**
* @param Request $request
* @return JsonResponse
* @throws \App\Classes\Exceptions\AccessForbiddenException
* @throws \App\Classes\Exceptions\MalformedRequestException
* @throws \App\Classes\Exceptions\RequestValidationException
*/
public function logic(Request $request) : JsonResponse
{
$query = $this->fetchesJobResult->execute(['job_id' => $request->route('job_id')]);
return $this->resourceResponse(new JobResultResource($query));
}
}
@@ -0,0 +1,94 @@
<?php
namespace App\Classes\Modules\Jobs\DataTransferObjects;
use App\Classes\General\Interfaces\DataTransferObject;
class ListGenericJobObject implements DataTransferObject
{
/** @var string */
private $name;
/** @var array */
private $payload;
/** @var string */
private $jobId;
/** @var object */
private $userInfo;
/** @var string */
private $jobCommandName;
/** @var string */
private $jobCommand;
public function __construct(string $name, array $payload, string $jobId, object $userInfo = null)
{
$this->name = $name;
$this->payload = $payload;
$this->jobId = $jobId;
$this->userInfo = $userInfo;
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @return array
*/
public function getPayload(): array
{
return $this->payload;
}
/**
* @return string
*/
public function getJobId(): string
{
return $this->jobId;
}
/**
* @return object
*/
public function getUserInfo(): object
{
return $this->userInfo;
}
/**
* @return string
*/
public function getJobCommandName(): string
{
return $this->jobCommandName;
}
/**
* @return string
*/
public function getJobCommand(): string
{
return $this->jobCommand;
}
public function setJobCommandName(string $jobCommandName)
{
$this->jobCommandName = $jobCommandName;
}
public function setJobCommand(string $jobCommand)
{
$this->jobCommand = $jobCommand;
}
}
@@ -0,0 +1,28 @@
<?php
namespace App\Classes\Modules\Jobs\Services;
use App\Classes\General\Eloquent\AbstractUpdateRecord;
use App\Models\JobResult;
use App\Classes\Modules\Jobs\DataTransferObjects\ListGenericJobObject;
class CreatesJobResult extends AbstractUpdateRecord
{
/**
* @param ListGenericJobObject $listGenericJobObject
* @param string $result
* @return \Illuminate\Database\Eloquent\Model
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function execute(ListGenericJobObject $listGenericJobObject, string $result)
{
$model = new JobResult();
$model->job_id = $listGenericJobObject->getJobId();
$model->result = $result;
$model->url = $listGenericJobObject->getName();
$model->job_command_name = $listGenericJobObject->getJobCommandName();
$model->job_command = $listGenericJobObject->getJobCommand();
return $this->handler($model);
}
}
@@ -0,0 +1,33 @@
<?php
namespace App\Classes\Modules\Jobs\Services;
use App\Classes\General\Eloquent\AbstractFetchRecord;
use Illuminate\Database\Eloquent\Builder;
use App\Models\JobResult;
class FetchesJobResult extends AbstractFetchRecord
{
/** @var JobResult */
private $repository;
/**
* FetchesJobResult constructor.
* @param JobResult $repository
*/
public function __construct(JobResult $repository)
{
$this->repository = $repository;
}
/**
* @return Builder
*/
public function getRepository(): Builder
{
return $this->repository->newQuery();
}
}
@@ -0,0 +1,54 @@
<?php
namespace App\Classes\Modules\PackingLists\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Jobs\ListPackingListsJob;
use App\Classes\Modules\Jobs\DataTransferObjects\ListGenericJobObject;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class ListPackingListsJobLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => 'List Transaction Job',
'message' => 'You have successfully submit a job to list transactions'
];
}
/**
* @param Request $request
* @return JsonResponse
*/
public function logic(Request $request) : JsonResponse
{
$jobId = uniqid();
$user = Auth::user();
$userInfo = (object) [
'email' => $user->email,
'type' => $user->type,
];
$listGenericJobObject = new ListGenericJobObject(
$request->fullUrl(),
$request->all(),
$jobId,
$userInfo
);
ListPackingListsJob::dispatch($listGenericJobObject);
$result = [];
$result['job_id'] = $jobId;
return $this->response(['data' => $result]);
}
}
@@ -0,0 +1,47 @@
<?php
namespace App\Classes\Modules\PackingLists\Processors;
use App\Classes\Modules\PackingLists\Services\ListsPackingLists;
use App\Classes\Modules\Jobs\Services\CreatesJobResult;
use App\Classes\General\Helper;
use App\Classes\Modules\Jobs\DataTransferObjects\ListGenericJobObject;
use App\Http\Resources\ListPackingListJobResource;
class ListPackingListsJobProcessor
{
/** @var ListsPackingLists */
private $listsPackingLists;
/** @var CreatesJobResult */
private $createsJobResult;
/**
* ListPackingListsJobProcessor constructor.
* @param ListsPackingLists $listsPackingLists
* @param CreatesJobResult $createsJobResult
*/
public function __construct(ListsPackingLists $listsPackingLists, CreatesJobResult $createsJobResult)
{
$this->listsPackingLists = $listsPackingLists;
$this->createsJobResult = $createsJobResult;
}
/**
* @param ListGenericJobObject $listGenericJobObject
* @return null|object
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function execute(ListGenericJobObject $listGenericJobObject) {
$query = $this->listsPackingLists->execute($this->listsPackingLists->deserializeFilters($listGenericJobObject->getPayload()['filters']), ['page' => $listGenericJobObject->getPayload()['page']]);
foreach ($query->items() as &$item) {
$item['userInfo'] = $listGenericJobObject->getUserInfo();
}
$result = Helper::collectionResponse(ListPackingListJobResource::collection($query));
$create = $this->createsJobResult->execute($listGenericJobObject, json_encode($result));
return $create;
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers\Jobs;
use App\Classes\Modules\Jobs\ControllersLogic\FetchJobResultLogic;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class FetchJobResultController
{
/**
* @param Request $request
* @param FetchJobResultLogic $logic
* @return JsonResponse
*/
public function fetch(Request $request, FetchJobResultLogic $logic): JsonResponse {
return $logic->execute($request);
}
}
@@ -0,0 +1,21 @@
<?php
namespace App\Http\Controllers\PackingLists;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use App\Classes\Modules\PackingLists\ControllersLogic\ListPackingListsJobLogic;
class ListPackingListsJobController
{
/**
* @param Request $request
* @param ListPackingListsJobLogic $logic
* @return JsonResponse
*/
public function list(Request $request, ListPackingListsJobLogic $logic) : JsonResponse {
return $logic->execute($request);
}
}
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class JobResultResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'job_id' => $this->job_id,
'result' => $this->result,
];
}
}
@@ -0,0 +1,44 @@
<?php
namespace App\Http\Resources;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\PackingListType;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Classes\ValueObjects\Constants\RoleTypes;
use App\Models\Order;
use App\Models\PackingList;
use Illuminate\Http\Resources\Json\JsonResource;
use App\Http\Resources\V2\PackingListV2Resource;
class ListPackingListJobResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
$exceptionUsers = in_array($this->userInfo ? $this->userInfo->type : Auth()->user()->type, [RoleTypes::SHADOW_ADMIN, RoleTypes::SUPER_ADMIN]);
$packages = !$this->packingLists()->exists() || $exceptionUsers ? $this->packages : $this->packingLists->first()->packages;
return [
'id' => $this->id,
'claimant_id' => $this->claimant_id,
'reference' => $this->reference,
'status' => $this->status,
'transport' => new TransportResource($this->transports()->first()),
'type' => $this->type,
'receive_packing_list' => $this->when($this->type === PackingListType::SHIPPING_PACKING_LIST, new PackingListV2Resource(PackingList::where('reference', $this->reference)->where('type', PackingListType::WAREHOUSE_RECEIVE_LIST)->first(), $this->userInfo)),
'packages' => PackageResource::collection($packages),
$this->mergeWhen($this->owner instanceof Order, [
'order' => New OrderResource($this->owner)
]),
'shipping_transaction' => new TransactionResource($this->transactions()->where('type', TransactionType::SHIPPING_INVOICE)->whereNotIn('status', [ApprovalStatus::SUSPENDED, ApprovalStatus::EXPIRED])->first()),
'suspended_invoice' => new TransactionResource($this->transactions()->where('type', TransactionType::SHIPPING_INVOICE)->whereIn('status', [ApprovalStatus::SUSPENDED])->first()),
// 'suspended_invoices' => TransactionResource::collection($this->transactions()->where('type', TransactionType::SHIPPING_INVOICE)->whereIn('status', [ApprovalStatus::SUSPENDED])->get()),
];
}
}
@@ -0,0 +1,54 @@
<?php
namespace App\Http\Resources\V2;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\PackingListType;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Classes\ValueObjects\Constants\RoleTypes;
use App\Models\Order;
use App\Models\PackingList;
use Illuminate\Http\Resources\Json\JsonResource;
use App\Http\Resources as V1;
class PackingListV2Resource extends JsonResource
{
private $userInfo;
public function __construct($resource, $userInfo = null)
{
parent::__construct($resource);
$this->userInfo = $userInfo;
}
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
$user = $this->userInfo ? $this->userInfo : Auth()->user();
$exceptionUsers = in_array($user->type, [RoleTypes::SHADOW_ADMIN, RoleTypes::SUPER_ADMIN]);
$packages = !$this->packingLists()->exists() || $exceptionUsers ? $this->packages : $this->packingLists->first()->packages;
return [
'id' => $this->id,
'claimant_id' => $this->claimant_id,
'reference' => $this->reference,
'status' => $this->status,
'transport' => new V1\TransportResource($this->transports()->first()),
'type' => $this->type,
'receive_packing_list' => $this->when($this->type === PackingListType::SHIPPING_PACKING_LIST, new PackingListV2Resource(PackingList::where('reference', $this->reference)->where('type', PackingListType::WAREHOUSE_RECEIVE_LIST)->first(), $user)),
'packages' => V1\PackageResource::collection($packages),
$this->mergeWhen($this->owner instanceof Order, [
'order' => New V1\OrderResource($this->owner)
]),
'shipping_transaction' => new V1\TransactionResource($this->transactions()->where('type', TransactionType::SHIPPING_INVOICE)->whereNotIn('status', [ApprovalStatus::SUSPENDED, ApprovalStatus::EXPIRED])->first()),
'suspended_invoice' => new V1\TransactionResource($this->transactions()->where('type', TransactionType::SHIPPING_INVOICE)->whereIn('status', [ApprovalStatus::SUSPENDED])->first()),
// 'suspended_invoices' => V1\TransactionResource::collection($this->transactions()->where('type', TransactionType::SHIPPING_INVOICE)->whereIn('status', [ApprovalStatus::SUSPENDED])->get()),
];
}
}
+13
View File
@@ -0,0 +1,13 @@
<?php
namespace App\Models;
class JobResult extends AbstractModel
{
protected $table = 'job_results';
public $fillable = [
'job_id',
'result'
];
}
@@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateJobResultsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('job_results', function (Blueprint $table) {
$table->id();
$table->string('job_id', 50);
$table->longText('result');
$table->timestamps();
// $table->foreign('job_id')->references('id')->on('jobs')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('job_results');
}
}
@@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddNewColumnToJobResultsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('job_results', function (Blueprint $table) {
$table->longText('url')->after('result')->nullable();
$table->string('job_command_name')->after('url')->nullable();
$table->longText('job_command')->after('job_command_name')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('job_results', function (Blueprint $table) {
$table->dropColumn('url');
$table->dropColumn('job_command_name');
$table->dropColumn('job_command');
});
}
}
@@ -0,0 +1,186 @@
<template>
<transition-component group enter-class="animate__animated animate__fadeInUp animate__delay-1 animate__faster p-r-30" leave-class="animate__animated animate__fadeOutDown animate__faster p-r-30" style="min-height: 300px;width:100%">
<loading-component style="height: 200px; top: 0;" key="1" color="success" v-show="isLoading"></loading-component>
<div class="row" key="2" v-show="!$store.getters.isLoading(section)">
<div class="col">
<div class="row">
<div class="col">
<div class="row align-items-center justify-content-center p-t-50 p-b-50" v-show="!$store.getters.getListData(section).length && !isLoading && emptyListSection">
<div class="col-10">
<div class="row align-items-center justify-content-center hint-text">
<div class="col-4 hint-text"><img src="/images/not-found-illustration.png" class="w-100 hint-text"/></div>
</div>
<div class="row text-center">
<div class="col">
<div class="row m-t-20">
<div class="col">
<p class="all-caps no-margin fs-11" style="letter-spacing: 2px;">Nothing To Show Here</p>
</div>
</div>
<div class="row m-t-5 align-items-center justify-content-center">
<div class="col">
<small class="fs-9 muted all-caps font-lato" style="letter-spacing: 2px">There is no results found, Try adjusting your filters to find what you are looking for.</small>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row" v-show="!isLoading">
<div class="col">
<div class="list disable-text-selection" data-check-all="checkAll">
<div class="row" ref="list" v-for="item in $store.getters.getListData(section)" v-bind:key="item.id" :data="item">
<div class="col">
<slot name="list" :data="item"></slot>
</div>
</div>
</div>
</div>
</div>
<div class="row" v-show="!isLoading">
<div class="col">
<pagination-component :section="section" class="mb-5" ref="pagination"></pagination-component>
</div>
</div>
</div>
</div>
</div>
</div>
</transition-component>
</template>
<script>
import requestV2 from '../../../general/mixins/aws/requestV2'
export default {
props: {
section:{
type: String,
required: true
},
endpoint: {
type: String,
required: true
},
options: {
default () {
return {}
}
},
emptyListSection: {
type: Boolean,
default: true,
}
},
data(){
return {
filters: this.options,
pollingInterval: null,
isPolling: false,
isLoading: false,
}
},
created(){
this.setDecoratorDefault();
this.$store.dispatch('updateListQueue', {'name': this.section, 'page': 1, 'filters': this.filters}); //cief todo: Uncaught (in promise) null
},
computed: {
pendingList () {
return this.$store.getters.isInCompleteQueue(this.section);
}
},
watch: {
pendingList(inComplete){
if(inComplete){
this.fetchList();
}
}
},
methods: {
fetchList(){
let listDecorators = this.$store.getters.getListDetails(this.section);
let url = this.endpoint + '?page=' + listDecorators.page + '&filters=' + JSON.stringify(listDecorators.filters);
this.isLoading = true;
this.submitJob(url);
},
successHandler(response){
let result = JSON.parse(response.payload.data.result);
result.meta = {
current_page: result.meta.current_page,
first_page_url: result.meta.first_page_url,
from: result.meta.from,
last_page: result.meta.last_page,
last_page_url: result.meta.last_page_url,
next_page_url: result.meta.next_page_url,
path: result.meta.path,
per_page: result.meta.per_page,
prev_page_url: result.meta.prev_page_url,
to: result.meta.to,
total: result.meta.total
};
this.stopPolling();
this.$store.dispatch('completeList', {'name': this.section, 'data': result.data});
this.$refs.pagination.makePagination(result.meta, result.links);
this.isLoading = false;
},
errorHandler(error){
this.isPolling = false;
},
startPolling(jobId, maxAttempts = 3) {
let attempts = 0;
const pollJobResult = () => {
if (this.isPolling) {
return;
}
this.isPolling = true;
attempts++;
if (attempts > maxAttempts) {
clearInterval(this.pollingInterval);
this.pollingInterval = null;
this.isPolling = false;
this.isLoading = false;
console.log(`Reached maximum attempts (${maxAttempts}). Polling stopped.`);
return;
}
this.fetchJobResult(jobId);
};
// pollJobResult(); // Initial call
this.pollingInterval = setInterval(pollJobResult, 10000);
},
stopPolling() {
clearInterval(this.pollingInterval);
this.pollingInterval = null;
this.isPolling = false;
},
submitJob(url){
try {
this.$store.dispatch('crudRequestV2', {endpoint: url, method: 'get'}).then(response => {
let success = response.ok;
response.json().then(response => {
if(!success){return;}
let jobId = response.payload.data.job_id;
if(jobId){
this.startPolling(jobId);
}
});
})
} catch (error) {
console.error('Error submitJob', error);
}
},
fetchJobResult(jobId) {
try {
let anotherEndpoint = route('api.job.fetch', jobId);
this.poll(anotherEndpoint, 'get', this.section, false, false); //cief todo: Uncaught (in promise) null
} catch (error) {
console.error('Error fetchJobResult', error);
}
}
},
mixins: [requestV2]
}
</script>
@@ -34,11 +34,17 @@
</div>
</div>
</div>
<list-component :key="currentKey" :section="section" :endpoint="route('api.packing_list.list')" :options="options">
<!-- CIEF TODO: For easy revert to old code -->
<!-- <list-component :key="currentKey" :section="section" :endpoint="route('api.packing_list.list')" :options="options">
<template slot="list" slot-scope="{data}">
<admin-payments-billing-component :data="data" :invoice_status="invoice_status" :section="section" ></admin-payments-billing-component>
</template>
</list-component>
</list-component> -->
<list-polling-component :key="currentKey" :section="section" :endpoint="route('api.packing_list.list.job')" :options="options">
<template slot="list" slot-scope="{data}">
<admin-payments-billing-component :data="data" :invoice_status="invoice_status" :section="section" ></admin-payments-billing-component>
</template>
</list-polling-component>
</div>
</div>
</template>
+49
View File
@@ -0,0 +1,49 @@
export default {
methods: {
poll(url, method, section, successNotification = true, errorNotification = true){
if(!this.validate()){ return; }
if (section) {
this.$store.dispatch('toggleLoading', {name: section, status: true})
}
this.$store.dispatch('crudRequestV2', {
endpoint: url,
method: method,
parameters: this.parameters
}).then(response => {
let statusCode = response.status,
success = response.ok;
response.json().then(response => {
if(!success){
this.openModal();
errorNotification ? this.$store.dispatch('createNotification', {title: response.title, message: response.message, type: 'error'}): null;
this.errorHandler(response, statusCode); return;
}
successNotification ? this.$store.dispatch('createNotification', {title: response.title, message: response.message, type: 'success'}): null;
this.successHandler(response)
});
}).catch((error) => {
this.$store.dispatch('createNotification', {title: 'Unexpected Error', message: 'An unexpected error has occurred. Try again!', type: 'error'});
}).then(() => {
if (section) {
this.$store.dispatch('toggleLoading', {name: section, status: false})
}
})
},
validate() {
if(this.$v){
this.$v.$touch();
return !this.$v.$invalid;
}
return true;
},
successHandler(response){},
errorHandler(response){}
}
}
+1 -26
View File
@@ -1,20 +1,7 @@
export default {
actions: {
crudRequest({getters, dispatch}, {endpoint, method, parameters}){
const queryDomain = endpoint.split('?')[0];
let encodedParams = endpoint.split('?')[1];
let decodedParams = fullyDecodeURI(encodedParams);
const queryParams = encodeURIComponent(decodedParams);
encodedParams = queryParams.toString();
let filteredEncodedParams = encodedParams.replace(/%3D/g,'=');
filteredEncodedParams = filteredEncodedParams.replace(/%26/g,'&');
let combinedAbsoluteUrl = queryDomain;
if(filteredEncodedParams !== undefined && filteredEncodedParams !== 'undefined'){
combinedAbsoluteUrl = queryDomain + '?' + filteredEncodedParams;
}
// return fetch(endpoint, {
return fetch(queryDomain + '?' + filteredEncodedParams, {
return fetch(endpoint, {
method: method,
responseType: 'json',
body: parameters ? JSON.stringify(parameters):null,
@@ -33,15 +20,3 @@ export default {
}
}
}
function isEncoded(uri) {
uri = uri || '';
return uri !== decodeURIComponent(uri);
}
function fullyDecodeURI(uri){
while (isEncoded(uri)){
uri = decodeURIComponent(uri);
}
return uri;
}
+47
View File
@@ -0,0 +1,47 @@
export default {
actions: {
crudRequestV2({getters, dispatch}, {endpoint, method, parameters}){
const queryDomain = endpoint.split('?')[0];
let encodedParams = endpoint.split('?')[1];
let decodedParams = fullyDecodeURI(encodedParams);
const queryParams = encodeURIComponent(decodedParams);
encodedParams = queryParams.toString();
let filteredEncodedParams = encodedParams.replace(/%3D/g,'=');
filteredEncodedParams = filteredEncodedParams.replace(/%26/g,'&');
let combinedAbsoluteUrl = queryDomain;
if(filteredEncodedParams !== undefined && filteredEncodedParams !== 'undefined'){
combinedAbsoluteUrl = queryDomain + '?' + filteredEncodedParams;
}
// return fetch(endpoint, {
return fetch(combinedAbsoluteUrl, {
method: method,
responseType: 'json',
body: parameters ? JSON.stringify(parameters):null,
headers: {
'content-type': 'application/json',
'Authorization': 'Bearer '+getters.getAccessToken
}
}).then(response => {
if(response.status === 401 && window.location.href !== route('login') && window.location.href.indexOf(route('last_mile_delivery.login')) <= -1){
dispatch('userAuthentication', {access_token: '', redirect_url: [7, 8].includes(getters.getCompanyModuleType) ? route('last_mile_delivery.login') : route('login')});
}
return response;
})
}
}
}
function isEncoded(uri) {
uri = uri || '';
return uri !== decodeURIComponent(uri);
}
function fullyDecodeURI(uri){
while (isEncoded(uri)){
uri = decodeURIComponent(uri);
}
return uri;
}
+3 -1
View File
@@ -4,6 +4,7 @@ import toggleSection from './modules/toggleSection'
import toggleLoading from './modules/toggleLoading'
import createNotification from './modules/createNotification'
import crudRequest from './modules/crudRequest'
import crudRequestV2 from './modules/crudRequestV2'
import authentication from './modules/authentication'
import loadRequestQueue from './modules/loadRequestQueue'
@@ -16,6 +17,7 @@ export default new Vuex.Store({
loadRequestQueue,
createNotification,
crudRequest,
crudRequestV2,
authentication
}
})
})
+2
View File
@@ -70,6 +70,8 @@ Route::group(['middleware' => 'api', 'prefix' => 'v1', 'as' => 'api.'], function
require __DIR__ . '/help_menu.php';
require __DIR__ . '/job.php';
});
require __DIR__ . '/announcement.php';
+7
View File
@@ -0,0 +1,7 @@
<?php
use Illuminate\Support\Facades\Route;
Route::group(['prefix' => 'job', 'as' => 'job.', 'namespace' => 'Jobs'], function () {
Route::get('/fetch/{job_id}', 'FetchJobResultController@fetch')->name('fetch');
});
+1
View File
@@ -5,6 +5,7 @@ use Illuminate\Support\Facades\Route;
Route::group(['namespace' => 'PackingLists', 'as' => 'packing_list.', 'prefix' => 'packing_list'], function () {
Route::get('/{id}/show', 'FetchPackingListController@fetch')->name('show');
Route::get('/list', 'ListPackingListsController@list')->name('list');
Route::get('/list/job', 'ListPackingListsJobController@list')->name('list.job');
Route::post('/create', 'CreatePackingListController@create')->name('create');
Route::post('/create/deliver', 'CreateDeliverPackingListController@create')->name('create.deliver');