15000ms TTFB waiting time with Nuxt and Laravel - laravel

I am experiencing a very long TTFB time, around 15000/17000ms with a GET request. This is happening only with one specific call, the rest are fine.
I started experiencing this only after adding Nuxt Auth and Laravel Sanctum. The request remains in pending (under the debugger network tab) for around 10 seconds before completing the request and giving the JSON result.
Here is my nuxt.confing.js
export default {
srcDir: 'resources/nuxt',
ssr: false,
head: {
titleTemplate: '%s - ' + process.env.APP_VERSION,
title: process.env.APP_NAME || '',
meta: [
{ charset: 'utf-8' },
{
name: 'viewport',
content: 'width=device-width, initial-scale=1'
},
{
hid: 'description',
name: 'description',
content: process.env.npm_package_description || ''
}
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
{ rel: 'stylesheet', href: 'https://raw.githack.com/lucperkins/bulma-dashboard/master/dist/bulma-dashboard.css' }
]
},
loading: { color: '#fff' },
css: [
'#/assets/main.scss'
],
plugins: [
"~/plugins/vee-validate.js"
],
components: true,
buildModules: [
'#nuxtjs/dotenv',
'#nuxtjs/eslint-module',
'#nuxtjs/fontawesome',
'#nuxtjs/moment',
],
modules: [
'nuxt-laravel',
// Doc: https://axios.nuxtjs.org/usage
'#nuxtjs/axios',
'nuxt-buefy',
'nuxt-fontawesome',
'#nuxtjs/auth-next'
],
build: {
transpile: [/#fullcalendar.*/,"vee-validate/dist/rules"],
extend(config, ctx) {
config.module.rules.push({
enforce: 'pre',
test: /\.(js|vue)$/,
loader: 'eslint-loader',
exclude: /(node_modules)/,
options: {
fix: true
}
})
}
},
axios: {
baseURL: process.env.API_URL,
debug: true,
credentials: true
},
auth: {
redirect: {
login: '/login',
logout: '/',
callback: '/login',
home: '/dashboard/'
},
strategies: {
'laravelSanctum': {
provider: 'laravel/sanctum',
url: process.env.API_URL
}
},
localStorage: false
},
buefy: {
materialDesignIcons: false,
defaultIconPack: 'fas',
defaultIconComponent: 'font-awesome-icon'
},
router: {
base: '/dashboard/',
linkActiveClass: 'is-active',
middleware: ['auth']
},
fontawesome: {
icons: {
solid: true
}
}
}
Nuxt page (I put only the js code for convenience)
<script>
// https://www.tutsmake.com/laravel-vue-js-full-calendar-example/
import FullCalendar from '#fullcalendar/vue'
import timeGridPlugin from '#fullcalendar/timegrid'
import resourceTimelinePlugin from '#fullcalendar/resource-timeline'
export default {
components: {
FullCalendar
},
data() {
return {
sessions: [],
todayDisabled: true,
calTitle: '',
calendarOptions: {
plugins: [timeGridPlugin, resourceTimelinePlugin],
schedulerLicenseKey: 'CC-Attribution-NonCommercial-NoDerivatives',
initialView: 'timeGridWeek',
refetchResourcesOnNavigate: true,
-->> resources: '/api/sessions', //the very long call
eventDisplay: 'block',
contentHeight: 'auto',
nowIndicator: true,
locale: 'en-gb',
timezone: 'Europe/London', // without this, after Daylight Saving Time the event goes 1 hour back
headerToolbar: false,
businessHours: [
{
daysOfWeek: [1, 2, 3, 4, 5],
startTime: '08:00',
endTime: '20:00'
},
{
daysOfWeek: [6],
startTime: '9:00',
endTime: '14:00'
}
],
slotMinTime: '07:00:00',
slotMaxTime: '24:00:00',
expandRows: true,
eventClick: (calendar) => {
this.$router.push({
name: 'calendar-id-sessiondate',
params: {
id: calendar.event.id,
sessiondate: this.$moment(calendar.event.start).format(
'YYYY-MM-DD'
)
}
})
},
datesSet: (dateInfo) => {
this.calTitle = dateInfo.view.title
this.todayDisabled = this.$moment().isBetween(
dateInfo.start,
dateInfo.end
)
}
}
}
}
}
</script>
Laravel Controller
The component "Fullcalendar" runs a GET request through "resources: '/api/sessions'" which goes to the following code.
private function getIntervalTasks($s, $start_period, $end_period)
{
$sessions = [];
foreach(CarbonPeriod::create($s->start_datetime, "$s->interval_num $s->interval_type_human", $s->end_datetime) as $start_session) {
if (Carbon::parse($start_session)->between($start_period, $end_period)) {
$canceled = false;
if ($s->exceptions->isNotEmpty()) {
foreach ($s->exceptions as $e) {
if (Carbon::parse($e->datetime)->toDateString() === $start_session->toDateString()) {
if($e->is_canceled) {
$canceled = true;
break;
} elseif ($e->is_rescheduled) {
$start_session = Carbon::parse($e->datetime);
}
}
}
}
if ($canceled) {
continue;
}
$end_session = Carbon::parse($start_session)->addMinutes($s->duration);
$sessions[] = [
'id' => (int)$s->id,
'title' => $s->client->name,
'start' => $start_session->format('Y-m-d H:i:s'),
'end' => $end_session->format('Y-m-d H:i:s'),
'className' => $s->status_colors
];
}
}
return $sessions;
}
public function index(Request $request) {
$start = (!empty($_GET["start"])) ? ($_GET["start"]) : ('');
$end = (!empty($_GET["end"])) ? ($_GET["end"]) : ('');
$session_period = SessionPattern::has('client')
->where(fn($q)=> $q->whereDate('start_datetime', '<=', $start)->orWhereDate('end_datetime', '>=', $end)
->orWhereBetween(DB::raw('date(`start_datetime`)'), [$start, $end])
->with('exceptions', fn($q) => $q->whereBetween(DB::raw('date(`datetime`)'), [$start, $end])
))->get();
$sessions = [];
foreach ($session_period as $session) {
if($session->is_recurrent){
foreach ($this->getIntervalTasks($session, $start, $end) as $s) {
$sessions[] = $s;
}
} else {
$items = ['none'];
}
}
return response()->json($sessions);
}
ps: I also tried to see if the problem was with Fullcalendar. With a axios call, the issue continues.

I am answering your question based on my similar experience.
But for accurate result i suggest to use php profiling tools like KCachegrind to find out which part of your code consumes more time to execute.
i Think The problem is With Carbon Which was Mine.
Carbon Object Takes Long time to instantiate (is Slow).
i have refactored my code to not use carbon for date compare (make it in DBMS) and everything speeds up.
The Probable Bottle Neck
I Think Your Problem is, you have fetched a lot of records from DB, and loop over them with foreach :
foreach ($this->getIntervalTasks($session, $start, $end) as $s) {
$sessions[] = $s;
}
then in getIntervalTasks you have a second loop :
foreach(CarbonPeriod::create($s->start_datetime, "$s->interval_num $s->interval_type_human", $s->end_datetime) as $start_session) {
it make the request execution in order of N*M .
Carbon::parse($start_session)->between($start_period, $end_period)
the above code Carbon::parse() is the slow code which is running in N*M (second degree) times.
Probable Solution
I Think Your Solution will be to Avoid Creating Carbon Object In This Order.
Implement Your Business Logic (Time Compare Logic) In DBMS (With Store Procedure Or DB Function) Would Solve The TTFB Issue.
Hypothesis Test
place this
$start = microtime(true);
foreach ($session_period as $session){
...
}
$time_elapsed_secs = microtime(true) - $start;
return response()->json(array_merge($sessions, $time_elapsed_secs);
you can find the time consuming code section using this technique.
and obviously test if my suggestion was correct or not.
NOTE: returning $time_elapsed_secs is in micro second.

Related

Nuxt/i18n: how to change head meta description?

How can I change the head meta tag og:description content value that is added by i18n as default?
I know I can rewrite it in every page with head function, but I'm looking for better way.
Maybe the best way is to force i18n to fill the og:description content value with the value of the description tag content value.
I had the same problem and couldn't find a fancy methods out of the box. So I came up with this solution:
export default {
computed: {
locale() {
return this.$i18n.localeProperties.code;
}
},
head() {
const heads = {
'en': {
title: 'English Title',
meta: [
{
hid: 'description',
name: 'description',
content: 'Home page description'
}
],
},
'de': {
title: 'Deutscher Titel',
meta: [
{
hid: 'description',
name: 'description',
content: 'Home page description'
}
],
}
};
return heads[this.locale];
}
}
Create an object headsfor all the languages you want to support and let the head function return the proper one based on the locale variable. I used a computed property, because it is also used in the template.
If you want to have the same value for og:description on every page, you can set it in nuxt.config.js or in /layout/default.vue. Unless you specify a new value in /pages/yourPage.vue this will make the tag value the same on every page.
In the meta of you head in nuxt.config.js add the following:
export default {
head: {
meta: [
{ hid: 'og:description', property: 'og:description' content: 'My own description' },
],
}
}
You can do the same with og:title, etc.
Based on #doebi 's approach I came up with the following solution to the problem.
export default {
head() {
return this.headData
}
computed: {
headData() {
const t = this.$i18n.t
return {
title: t('thispages.title'),
meta: [
{
hid: 'description',
name: 'description',
content: t('thispages.description')
}
],
}
}
},
...
}
And in Nuxt with composition API we can utilize useMeta
export default {
setup(){
const
{ t } = useI18n() // from 'vue-i18n'
useMeta({ // from '#nuxtjs/composition-api
title: t('thispages.title'),
meta: [
{
hid: 'description',
name: 'description',
content: t('thispages.description')
}
]
})
},
...
}

How to make a form validation in InertiaJS

I am follow this Link for a inertiaJS tutorial. BTW I'm using laravel 8
I have this image below from my webpage. I don't know why I don't have a page>errors in picture given. Is there any other way to validate a form other than the link that I provided?.
I commented out the page.error in my HTML because it causes me an error Error in render: "TypeError: Cannot read property 'email' of undefined" because of my Page.Error is an empty object.
Update : Code for my controller
Script Code
<script>
import Layout from "../../Shared/Layout";
export default {
components: {
Layout,
},
data() {
return {
lead: {
name: "",
email: "",
phone: "",
dob: "",
interested_package: "",
},
};
},
methods: {
async handleSubmit() {
let res = await this.$inertia.post("/leads/save", this.lead);
},
},
};
</script>
App Service Provider
public function boot()
{
//
Schema::defaultStringLength(255);
Inertia::share([
'errors' => function () {
return Session::get('errors')
? Session::get('errors')->getBag('default')->getMessage()
: (object)[];
}
]);
Inertia::share('flash', function () {
return [
'message' => Session::get('message'),
'success' => Session::get('success'),
'error' => Session::get('error'),
];
});
}
To access the email error, context needs to be added via the this keyword ie. this.$page.props.errors.email.
Hey i think you are not sharing error bag in Controller that is why is empty ,to solve this, go to your AppServiceProvider
and add this code.
Inertia::share([
'errors' => function () {
return Session::get('errors')
? Session::get('errors')->getBag('default')->getMessages()
: (object) [];
},
]);

Laravel Yajra Datatables searchable not working on desire column

I have datatables column as such:
var showMasterUserTable = function () {
masterIcon = $('#master_user_table').DataTable({
processing: true,
serverSide: true,
responsive: true,
ajax: {
url: ROOT + 'master-voucher-bit-x/data',
},
columns: [
{
data: 'DT_RowIndex',
name: 'DT_RowIndex',
searchable: false,
orderable: false
},
{
data: 'voucher_code',
name: 'voucher_code',
},
{
data: 'status',
name: 'status',
},
{
data: 'track',
name: 'track',
},
{
data: 'user_use',
name: 'user_use',
orderable: true
},
{
data: 'progress',
name: 'progress',
},
{
data: 'quiz_score',
name: 'quiz_score',
},
{
data: 'urlQr',
name: 'urlQr',
}
]
});
};
As long as i know from the yajra and datatables docs that searchable and orderable is default to be true when it remains unwritten, i have encounter the issue where searchable only search for voucher_code column no matter if i set all the searchable to true. I want to search for user_use column instead. If i set all the searchable to false, the table data cannot be loaded. How should i overcome it? Here's my controller code:
{
$model = VoucherBitX::select('voucher_bit_x.*', 'users.email')
->join('users', 'voucher_bit_x.user_id', '=', 'users.id')
->orderBy("voucher_bit_x.redeem_at","DESC");
return DataTables::of($model)
->addColumn('status', function ($data) {
if($data->status > 0)
$status = '<div><span class="badge badge-success"> Available </span></div>';
else
$status = '<div><span class="badge badge-danger"> Not Available </span></div>';
return $status;
})
->addColumn('urlQr', function ($data) {
$user = UserApp::find($data->user_id);
$a = "";
if(!empty($user) && isset($user->ref_id)){
$quiz = QuizScore::where("track_id",$data->track_id)->where("user_id",$data->user_id)->first();
if($quiz && $quiz->status){
$track = Track::find($data->track_id);
$urlQr = 'https://xxx.id/api/certificate/'.base64_encode(json_encode(["user_id"=>$user->id,"slug"=>$track->slug,"track_id"=>$track->id]));
$a = 'Download Certificate';
}
}
return $a;
})
->addColumn('quiz_score', function ($data) {
$score = 0;
$quiz = QuizScore::where("track_id",$data->track_id)->where("user_id",$data->user_id)->first();
if($quiz){
$score = $quiz->score;
}
return $score;
})
->addColumn('progress', function ($data) {
$progress = 0;
$solve = Track::userProgress($data->user_id,$data->track_id);
if(!empty($solve)){
$progress = $solve;
}
return $progress."%";
})
->addColumn('user_use', function ($data) {
$user = UserApp::find($data->user_id);
if(!empty($user))
return $user->name." (".$user->email.")";
return '-';
})
->addColumn('track', function ($data) {
$track = Track::find($data->track_id);
return isset($track->title)?$track->title:"";
})->rawColumns(['quiz_score','status','user_use','track','urlQr'])
->addIndexColumn()->make(true);
}
*Edit:
I have realized that datatables returned a response that included used query like this:
New question: just where the hell that query json field configuration? On my eloquent query above there is no such thing as where and like query. Haven't found that things both in yajra and datatables documentation. What i want is to modify the where field to users.email instead voucher_bit_x.voucher_code
use columns.searchable
Using this parameter, you can define if DataTables should include this column in the filterable data in the table. You may want to use this option to disable search on generated columns such as 'Edit' and 'Delete' buttons for example.
$('#example').dataTable( {
"columnDefs":
[
{ "searchable": false, "targets": 0 }
]
});
This will disable the search of multiple columns as specified n the target. If you want multiple columns then try using
{ "searchable": false, "targets": [0,1,2] }
Where targets 0 1 and 2 are number of columns starting index from 0

Making a request and filtering records in controller before send out to endpoint

I have more then 8000 records in DB. I need a solid pagination and filtering functionality. To accomplish that I am trying to filter the records before paginate and send out those to endpoint.
For example in controller I request the keyword and check the keyword using →when() if there is keyword then filter records with that and →paginate(20)
in controller
public function bridal(Request $request)
{
$keyword = $request->get("keyword");
$bridals = Place::with(["plans" => function($query)
{
$query->orderBy("plans.plan_price");
}
])
->whereHas("plans")
->groupBy("address")
->when($keyword, function($query, $keyword){
return $query->where("city", $keyword);
})
->paginate(20);
return $bridals;
}
Route
Route::match(["GET", "POST"], "bridal", "Api\BridalController#bridal");
I guess everything is fine 'till here. So let's continue in frontend side.
in vuex: Store.js
state: {
bridals: [],
keyword: "",
},
mutations: {
setBridals(state, bridal){
state.bridals = bridal;
},
setKeywords(state, keys){
state.keyword = keys;
},
},
getters: {
getBridals(state){
return state.bridals;
},
},
actions: {
bridalApi({commit}, payload){
axios.get("api/bridal?page="+payload.page, {
keyword: payload.forms.keyword
})
.then(response => {
this.commit("setBridals", response.data);
})
.catch(e => {
console.log(e);
})
},
}
and in home component I am sending filter params to controller.
<form #submit.prevent="submit" method="post">
<search-bar />
<submit-btn />
</form>
mounted(){
this.$store.dispatch("bridalApi", {
page: this.currentPage,
forms: this.filterParams,
});
},
methods: {
submit(){
this.$store.dispatch("bridalApi", {
forms: this.filterParams,
});
},
},
computed: {
filterParams(){
let paramObj = {
keyword: this.$store.state.bridal.keyword,
}
return paramObj;
}
},
I am not sure what is wrong in the code. Where do I make mistake.
P.S: By the way I set and get the keyword state.keyword... it's working fine. I can get the value. I just didn't add input code here...

Am i missing something, this.form.fill() doesn't seems to work for me

My UserController Function:
public function profile()
{
return auth('api')->user();
}
i'm working on project that frontend with vue and backend with laravel.
My Vue.js Code:
import Form from 'vform';
export default {
data () {
return {
form: new Form({
id:'',
name: '',
email: '',
password: '',
type:'',
bio:'',
photo:''
})
}
},
mounted() {
console.log('Component mounted.')
},
created(){
axios.get("api/profile")
.then( ({data}) => (this.form.fill(data)))
}
}
i try to show data in form, but when i call this function it not return the data. what i am doing wrong? i know there is problem with this function this.form.fill(data) but i don't know what actual the problem is.

Resources