Skip to content

Commit 59039ec

Browse files
(v2.7) implements events listener
1 parent 8aae0aa commit 59039ec

File tree

9 files changed

+198
-63
lines changed

9 files changed

+198
-63
lines changed

config/request-docs.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
],
2929

3030
'hide_meta_data' => false,
31+
'hide_sql_data' => false,
32+
'hide_logs_data' => false,
33+
'hide_models_data' => false,
3134

3235
// https://github.com/rakutentech/laravel-request-docs/pull/92
3336
// When rules are put in other method than rules()

src/LaravelRequestDocsMiddleware.php

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,31 @@
77
use Illuminate\Support\Facades\DB;
88
use KitLoong\AppLogger\QueryLog\LogWriter as QueryLogger;
99
use Log;
10+
use Event;
11+
use Str;
1012

1113
class LaravelRequestDocsMiddleware extends QueryLogger
1214
{
1315
private array $queries = [];
1416
private array $logs = [];
17+
private array $models = [];
1518

1619
public function handle($request, Closure $next)
1720
{
1821
if (!$request->headers->has('X-Request-LRD') || !config('app.debug')) {
1922
return $next($request);
2023
}
2124

22-
$this->listenDB();
23-
$this->listenToLogs();
25+
if (!config('request-docs.hide_sql_data')) {
26+
$this->listenToDB();
27+
}
28+
if (!config('request-docs.hide_logs_data')) {
29+
$this->listenToLogs();
30+
}
31+
if (!config('request-docs.hide_models_data')) {
32+
$this->listenToModels();
33+
}
34+
2435
$response = $next($request);
2536

2637
try {
@@ -34,6 +45,7 @@ public function handle($request, Closure $next)
3445
$content->_lrd = [
3546
'queries' => $this->queries,
3647
'logs' => $this->logs,
48+
'models' => $this->models,
3749
'memory' => (string) round(memory_get_peak_usage(true) / 1048576, 2) . "MB",
3850
];
3951
$jsonContent = json_encode($content);
@@ -51,7 +63,7 @@ public function handle($request, Closure $next)
5163
return $response;
5264
}
5365

54-
public function listenDB()
66+
public function listenToDB()
5567
{
5668
DB::listen(function (QueryExecuted $query) {
5769
$this->queries[] = $this->getMessages($query);
@@ -63,4 +75,34 @@ public function listenToLogs()
6375
$this->logs[] = $message;
6476
});
6577
}
78+
79+
public function listenToModels()
80+
{
81+
Event::listen('eloquent.*', function ($event, $models) {
82+
foreach (array_filter($models) as $model) {
83+
// doing and booted ignore
84+
if (Str::startsWith($event, 'eloquent.booting')
85+
|| Str::startsWith($event, 'eloquent.retrieving')
86+
|| Str::startsWith($event, 'eloquent.creating')
87+
|| Str::startsWith($event, 'eloquent.saving')
88+
|| Str::startsWith($event, 'eloquent.updating')
89+
|| Str::startsWith($event, 'eloquent.deleting')
90+
) {
91+
continue;
92+
}
93+
// split $event by : and take first part
94+
$event = explode(':', $event)[0];
95+
$event = Str::replace('eloquent.', '', $event);
96+
$class = get_class($model);
97+
98+
if (!isset($this->models[$class])) {
99+
$this->models[$class] = [];
100+
}
101+
if (!isset($this->models[$class][$event])) {
102+
$this->models[$class][$event] = 0;
103+
}
104+
$this->models[$class][$event] = $this->models[$class][$event]+1;
105+
}
106+
});
107+
}
66108
}

ui/src/components/ApiAction.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import ApiActionTabs from './elements/ApiActionTabs'
99
import ApiActionInfo from './elements/ApiActionInfo'
1010
import ApiActionSQL from './elements/ApiActionSQL'
1111
import ApiActionLog from './elements/ApiActionLog'
12+
import ApiActionEvents from './elements/ApiActionEvents'
1213

1314
interface Props {
1415
lrdDocsItem: IAPIInfo,
@@ -32,7 +33,8 @@ export default function ApiAction(props: Props) {
3233
const [responseData, setResponseData] = useState("");
3334
const [sqlQueriesCount, setSqlQueriesCount] = useState(0);
3435
const [sqlData, setSqlData] = useState("");
35-
const [logData, setLogData] = useState("");
36+
const [modelsData, setModelsData] = useState({});
37+
const [logsData, setLogsData] = useState("");
3638
const [serverMemory, setServerMemory] = useState("");
3739
const [responseStatus, setResponseStatus] = useState(0);
3840
const [responseHeaders, setResponseHeaders] = useState("");
@@ -65,6 +67,7 @@ export default function ApiAction(props: Props) {
6567
}
6668

6769
const handleSendRequest = () => {
70+
updateLocalStorage()
6871
try {
6972
JSON.parse(requestHeaders)
7073
} catch (error: any) {
@@ -109,7 +112,7 @@ export default function ApiAction(props: Props) {
109112

110113
setSendingRequest(true)
111114
setSqlData("")
112-
setLogData("")
115+
setLogsData("")
113116
setServerMemory("")
114117
setResponseData("")
115118
setError(null)
@@ -143,24 +146,25 @@ export default function ApiAction(props: Props) {
143146
for (const value of data._lrd.logs) {
144147
logs += value.level + ": " + value.message + "\n"
145148
}
146-
setLogData(logs)
149+
setLogsData(logs)
147150
}
148151
if (data && data._lrd && data._lrd.memory) {
149152
setServerMemory(data._lrd.memory)
150153
}
154+
if (data && data._lrd && data._lrd.models) {
155+
setModelsData(data._lrd.models)
156+
}
151157
// remove key _lrd from response
152158
if (data && data._lrd) {
153159
delete data._lrd
154160
}
155161
setResponseData(JSON.stringify(data, null, 2))
156162
setActiveTab('response')
157-
updateLocalStorage()
158163
}).catch((error) => {
159164
setError("Response error: " + error)
160165
setResponseStatus(500)
161166
setSendingRequest(false)
162167
setActiveTab('response')
163-
updateLocalStorage()
164168
})
165169

166170
}
@@ -245,7 +249,8 @@ export default function ApiAction(props: Props) {
245249
activeTab={activeTab}
246250
responseStatus={responseStatus}
247251
sqlQueriesCount={sqlQueriesCount}
248-
logData={logData}
252+
logsData={logsData}
253+
modelsData={modelsData}
249254
setActiveTab={setActiveTab} />
250255

251256
<div className='mt-5'>
@@ -285,7 +290,10 @@ export default function ApiAction(props: Props) {
285290
)}
286291

287292
{activeTab == 'logs' && (
288-
<ApiActionLog logData={logData} />
293+
<ApiActionLog logsData={logsData} />
294+
)}
295+
{activeTab == 'events' && (
296+
<ApiActionEvents modelsData={modelsData} />
289297
)}
290298
</div>
291299
</>

ui/src/components/ApiInfo.tsx

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import React, { useState, useEffect } from 'react';
22
import shortid from 'shortid';
3-
import {explode} from '../libs/strings'
3+
import { explode } from '../libs/strings'
44
import type { IAPIInfo } from '../libs/types'
5-
import { ChevronRightIcon, LinkIcon, EnvelopeIcon } from '@heroicons/react/24/outline'
5+
import { ChevronRightIcon, LinkIcon, EnvelopeIcon } from '@heroicons/react/24/outline'
66

77
interface Props {
88
lrdDocsItem: IAPIInfo,
@@ -26,7 +26,7 @@ export default function ApiInfo(props: Props) {
2626
}
2727
}
2828
setHasFile(files.length > 0)
29-
}, [])
29+
}, [])
3030

3131
const StyledRule = (theRule: any): JSX.Element => {
3232
theRule = theRule.rule
@@ -38,18 +38,18 @@ export default function ApiInfo(props: Props) {
3838
<LinkIcon className='inline-block w-4 h-4' /> {theRule}
3939
</div>
4040
)
41-
}
41+
}
4242
if (theRule == 'email') {
4343
return (
4444
<div className="block">
4545
<EnvelopeIcon className='inline-block w-4 h-4' /> {theRule}
4646
</div>
4747
)
48-
}
48+
}
4949

5050
if (split.length < 2) {
5151
return (
52-
<div className='' dangerouslySetInnerHTML={{__html: explode(theRule, 50, "<br/>")}} />
52+
<div className='' dangerouslySetInnerHTML={{ __html: explode(theRule, 50, "<br/>") }} />
5353
)
5454
}
5555

@@ -81,8 +81,8 @@ export default function ApiInfo(props: Props) {
8181
}
8282

8383
return (
84-
<div className='' dangerouslySetInnerHTML={{__html: explode(theRule, 50, "<br/>")}} />
85-
)
84+
<div className='' dangerouslySetInnerHTML={{ __html: explode(theRule, 50, "<br/>") }} />
85+
)
8686
}
8787

8888
return (
@@ -126,7 +126,7 @@ export default function ApiInfo(props: Props) {
126126
<div key={shortid.generate()} className="block badge badge-success badge-outline ml-4 mt-1 mb-1 rounded-sm title">{theRule}</div>
127127
) : (<span key={shortid.generate()}></span>)
128128
))
129-
))}
129+
))}
130130
{lrdDocsItem.rules[key].map((rule) => (
131131
rule.split('|').map((theRule) => (
132132
(theRule == "required") ? (
@@ -152,12 +152,12 @@ export default function ApiInfo(props: Props) {
152152
if (theRule == "required") {
153153
return (<span key={shortid.generate()}></span>)
154154
}
155-
if (theRule == "integer"
156-
|| theRule == "string"
157-
|| theRule == "bool"
158-
|| theRule == "date"
159-
|| theRule == "file"
160-
|| theRule == "image"
155+
if (theRule == "integer"
156+
|| theRule == "string"
157+
|| theRule == "bool"
158+
|| theRule == "date"
159+
|| theRule == "file"
160+
|| theRule == "image"
161161
|| theRule == "array"
162162
|| theRule == "nullable") {
163163
return (
@@ -178,14 +178,14 @@ export default function ApiInfo(props: Props) {
178178
))}
179179
{lrdDocsItem.rules[key].map((rule) => (
180180
rule.split('|').map((theRule) => {
181-
if (theRule == "required"
182-
|| theRule == "integer"
183-
|| theRule == "string"
184-
|| theRule == "bool"
185-
|| theRule == "date"
186-
|| theRule == "file"
187-
|| theRule == "image"
188-
|| theRule == "array"
181+
if (theRule == "required"
182+
|| theRule == "integer"
183+
|| theRule == "string"
184+
|| theRule == "bool"
185+
|| theRule == "date"
186+
|| theRule == "file"
187+
|| theRule == "image"
188+
|| theRule == "array"
189189
|| theRule == "nullable") {
190190
return (<span key={shortid.generate()}></span>)
191191
}

ui/src/components/TopNav.tsx

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import React, { useEffect } from 'react';
22

33
import useLocalStorage from 'react-use-localstorage';
4-
import { MagnifyingGlassIcon,
5-
Cog6ToothIcon,
6-
ArrowUpRightIcon,
7-
MoonIcon,
8-
SunIcon,
9-
XMarkIcon,
10-
Bars3BottomLeftIcon,
11-
RectangleGroupIcon,
12-
FunnelIcon,
13-
CircleStackIcon,
14-
ChatBubbleLeftIcon } from '@heroicons/react/24/outline'
4+
import {
5+
MagnifyingGlassIcon,
6+
Cog6ToothIcon,
7+
ArrowUpRightIcon,
8+
MoonIcon,
9+
SunIcon,
10+
XMarkIcon,
11+
Bars3BottomLeftIcon,
12+
RectangleGroupIcon,
13+
FunnelIcon,
14+
CircleStackIcon,
15+
ChatBubbleLeftIcon
16+
} from '@heroicons/react/24/outline'
1517

1618
interface Props {
1719
handleChangeSettings: (
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import React from 'react';
2+
3+
import { explode } from '../../libs/strings'
4+
5+
interface Props {
6+
modelsData: any,
7+
}
8+
9+
export default function ApiActionEvents(props: Props) {
10+
const { modelsData } = props
11+
12+
return (
13+
<>
14+
{!Object.keys(modelsData).length && (
15+
<div className='text-center text-sm text-slate-500'>
16+
No Models Data
17+
</div>
18+
)}
19+
{Object.keys(modelsData).length != 0 && (
20+
<>
21+
<h3 className='title'>Models</h3>
22+
<p>
23+
<small className='text-slate-500'>Events are in the order of occurances</small>
24+
</p>
25+
<div className='divider'></div>
26+
{Object.keys(modelsData).map((model, index) => {
27+
return (
28+
<table className='table table-compact table-fixed table-zebra w-full mb-10' key={index}>
29+
<tbody>
30+
{Object.keys(modelsData[model]).map((event, idx) => {
31+
return (
32+
<tr key={idx}>
33+
{idx == 0 && (
34+
<td rowSpan={Object.keys(modelsData[model]).length}>
35+
<span className='font-bold text-slate-500'>Model</span>
36+
<br />
37+
<div className='' dangerouslySetInnerHTML={{ __html: explode(model.split('\\')[model.split('\\').length - 1], 30, "<br/>") }} />
38+
</td>
39+
)}
40+
<td className='capitalize'>{event}</td>
41+
<td>
42+
<span className='font-bold'>{modelsData[model][event]}</span>
43+
<span className='text-slate-400 pl-1'>
44+
Time{modelsData[model][event] > 1 ? 's' : ''}
45+
</span>
46+
</td>
47+
</tr>
48+
)
49+
})}
50+
</tbody>
51+
</table>
52+
)
53+
})}
54+
</>
55+
)}
56+
</>
57+
)
58+
}

0 commit comments

Comments
 (0)