Skip to content

Commit 45c1877

Browse files
Merge pull request #156 from rakutentech/feature/listen
(v2.7) Show Models activity upon api request
2 parents bda0780 + 59039ec commit 45c1877

File tree

10 files changed

+205
-69
lines changed

10 files changed

+205
-69
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ Fixing lints
186186
- v2.3 Bug fix for localstorage (tabs) and full UI refactored after alpha
187187
- v2.4 Show version on navbar and curl is using ace editor
188188
- v2.5 Groupby final fix and localstorage clear button. Other UI refactor
189+
- v2.6 File uploads
190+
- v2.7 Show activity on Eloquent models
189191

190192

191193
# Maintainers

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: 18 additions & 17 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: (
@@ -152,12 +154,12 @@ export default function TopNav(props: Props) {
152154
<Cog6ToothIcon className="inline-block h-6 w-6 mr-1" />
153155
Settings
154156
</h3>
155-
<hr />
157+
<div className='divider'></div>
156158
<h4 className="font-bold mt-10">
157159
<Bars3BottomLeftIcon className="inline-block h-6 w-6 mr-1" />
158160
Sort By
159161
</h4>
160-
<hr />
162+
<div className='divider'></div>
161163
<div className="form-control">
162164
<label className="label">
163165

@@ -175,8 +177,7 @@ export default function TopNav(props: Props) {
175177
<RectangleGroupIcon className="inline-block h-6 w-6 mr-1" />
176178
Group By
177179
</h4>
178-
<hr />
179-
<hr />
180+
<div className='divider'></div>
180181
<div className="form-control">
181182
<label className="label">
182183

@@ -194,7 +195,7 @@ export default function TopNav(props: Props) {
194195
<FunnelIcon className="inline-block h-6 w-6 mr-1" />
195196
Filter Settings
196197
</h4>
197-
<hr />
198+
<div className='divider'></div>
198199
<div className="form-control">
199200
<label className="label">
200201
<span className="label-text">GET</span>
@@ -225,7 +226,7 @@ export default function TopNav(props: Props) {
225226
<CircleStackIcon className="inline-block h-6 w-6 mr-1" />
226227
Storage
227228
</h4>
228-
<hr />
229+
<div className='divider'></div>
229230
<div className="form-control">
230231
<label className="label">
231232
<span className="label-text">

0 commit comments

Comments
 (0)