Skip to content
This repository was archived by the owner on Jul 10, 2024. It is now read-only.

Commit 566c289

Browse files
Add video download and bulk session delete functionalities (#97)
1 parent 107c733 commit 566c289

File tree

16 files changed

+16594
-5427
lines changed

16 files changed

+16594
-5427
lines changed

package-lock.json

Lines changed: 11077 additions & 1263 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "appium-dashboard",
3-
"version": "v1.0.0-beta.14",
3+
"version": "v2.0.0",
44
"description": "Appium plugin to view session execution details via detailed dashboard",
55
"main": "lib/index.js",
66
"scripts": {
@@ -74,5 +74,8 @@
7474
"@types/teen_process": "^1.16.0",
7575
"@types/uuid": "^8.3.1",
7676
"typescript": "^4.4.4"
77+
},
78+
"peerDependencies": {
79+
"appium": "^2.0.0-beta.46"
7780
}
7881
}

src/app/controllers/session-controller.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ export class SessionController extends BaseController {
1515
router.get("/", this.getSessions.bind(this));
1616
router.get("/:sessionId", this.getSessionBySessionId.bind(this));
1717
router.delete("/:sessionId", (req, res, next) => this.deleteSession(req, res, next, config));
18+
router.delete("/", (req, res, next) => this.deleteAllSession(req, res, next, config));
1819
router.get("/:sessionId/log/:logId/screen-shot", this.getScreenShotForLog.bind(this));
1920
router.get("/:sessionId/video", this.getVideoForSession.bind(this));
21+
router.get("/:sessionId/video/download", this.downloadVideoForSession.bind(this));
2022
router.get("/:sessionId/logs/text", this.getTextLogs.bind(this));
2123
router.get("/:sessionId/logs/device", this.getDeviceLogs.bind(this));
2224
router.get("/:sessionId/logs/debug", this.getDebugLogs.bind(this));
@@ -51,6 +53,37 @@ export class SessionController extends BaseController {
5153
);
5254
}
5355

56+
public async deleteAllSession(request: Request, response: Response, next: NextFunction, config: any) {
57+
let sessions = await Session.findAll({
58+
where: {
59+
session_status: {
60+
[Op.notIn]: ["RUNNING"],
61+
},
62+
},
63+
});
64+
65+
await Session.destroy({
66+
where: {
67+
session_status: {
68+
[Op.notIn]: ["RUNNING"],
69+
},
70+
},
71+
});
72+
73+
for (var session of sessions) {
74+
try {
75+
if (session.video_path) {
76+
fs.unlinkSync(session.video_path);
77+
}
78+
fs.rmdirSync(path.join(config.screenshotSavePath, session.session_id), { recursive: true });
79+
} catch (err) {}
80+
}
81+
82+
this.sendSuccessResponse(response, {
83+
success: true,
84+
});
85+
}
86+
5487
public async deleteSession(request: Request, response: Response, next: NextFunction, config: any) {
5588
let sessionId: string = request.params.sessionId;
5689
let session = await Session.findOne({
@@ -73,6 +106,22 @@ export class SessionController extends BaseController {
73106
}
74107
}
75108

109+
public async downloadVideoForSession(request: Request, response: Response, next: NextFunction) {
110+
let sessionId: string = request.params.sessionId;
111+
let session = await Session.findOne({
112+
where: {
113+
session_id: sessionId,
114+
},
115+
});
116+
const videoPath = session?.video_path;
117+
118+
if (session && videoPath) {
119+
return response.download(videoPath);
120+
} else {
121+
this.sendFailureResponse(response, "Video not available");
122+
}
123+
}
124+
76125
public async getVideoForSession(request: Request, response: Response, next: NextFunction) {
77126
let sessionId: string = request.params.sessionId;
78127
const range = request.headers.range;

src/plugin/session-manager.ts

Lines changed: 46 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -210,50 +210,58 @@ class SessionManager {
210210
}
211211

212212
private async sessionStarted(command: AppiumCommand) {
213-
sessionDebugMap.createNewSession(this.sessionInfo.session_id);
213+
try {
214+
sessionDebugMap.createNewSession(this.sessionInfo.session_id);
214215

215-
this.driverScriptExecutor = new DriverScriptExecutor(this.sessionInfo, command.driver);
216+
this.driverScriptExecutor = new DriverScriptExecutor(this.sessionInfo, command.driver);
216217

217-
/* Check if the current session supports network profiling */
218-
if (isHttpLogsSuppoted(this.sessionInfo)) {
219-
pluginLogger.info("Creating network profiler");
220-
this.httpLogger = getHttpLogger({
221-
sessionInfo: this.sessionInfo,
222-
adb: this.adb,
223-
driver: command.driver,
224-
});
225-
}
218+
/* Check if the current session supports network profiling */
219+
if (isHttpLogsSuppoted(this.sessionInfo)) {
220+
pluginLogger.info("Creating network profiler");
221+
this.httpLogger = getHttpLogger({
222+
sessionInfo: this.sessionInfo,
223+
adb: this.adb,
224+
driver: command.driver,
225+
});
226+
}
226227

227-
let { desired } = this.sessionInfo.capabilities;
228-
let buildName = desired["dashboard:build"];
229-
let projectName = desired["dashboard:project"];
230-
let name = desired["dashboard:name"];
231-
let build, project;
228+
let { desired } = this.sessionInfo.capabilities;
229+
let buildName = desired["dashboard:build"];
230+
let projectName = desired["dashboard:project"];
231+
let name = desired["dashboard:name"];
232+
let build, project;
232233

233-
let { is_profiling_available, device_info } = await this.startAppProfiling();
234-
await this.startHttpLogsCapture();
234+
let { is_profiling_available, device_info } = await this.startAppProfiling();
235+
await this.startHttpLogsCapture();
235236

236-
if (projectName) {
237-
project = await getOrCreateNewProject({ projectName });
238-
}
239-
if (buildName) {
240-
build = await getOrCreateNewBuild({ buildName, projectId: project?.id });
241-
}
237+
if (projectName) {
238+
project = await getOrCreateNewProject({ projectName });
239+
}
240+
if (buildName) {
241+
build = await getOrCreateNewBuild({ buildName, projectId: project?.id });
242+
}
242243

243-
await this.initializeScreenShotFolder();
244-
await this.startScreenRecording(command.driver);
245-
await Session.create({
246-
...this.sessionInfo,
247-
start_time: new Date(),
248-
build_id: build?.build_id,
249-
project_id: project?.id || null,
250-
device_info,
251-
is_profiling_available,
252-
name: name || null,
253-
live_stream_port: await getMjpegServerPort(command.driver, this.sessionInfo.session_id),
254-
} as any);
255-
256-
await this.saveCommandLog(command, null);
244+
await this.initializeScreenShotFolder();
245+
await this.startScreenRecording(command.driver);
246+
await Session.create({
247+
...this.sessionInfo,
248+
start_time: new Date(),
249+
build_id: build?.build_id,
250+
project_id: project?.id || null,
251+
device_info,
252+
is_profiling_available,
253+
name: name || null,
254+
live_stream_port: await getMjpegServerPort(command.driver, this.sessionInfo.session_id),
255+
} as any);
256+
257+
await this.saveCommandLog(command, null);
258+
} catch (err) {
259+
logger.error(
260+
`Error saving new session info in database for session ${
261+
this.sessionInfo.session_id
262+
}. response: ${JSON.stringify(err)}`
263+
);
264+
}
257265
}
258266

259267
public async sessionTerminated(options: { sessionTimedOut: boolean } = { sessionTimedOut: false }) {

src/plugin/utils/plugin-utils.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ function getSessionDetails(rawCapabilities: any, sessionResponse: any): any {
1616

1717
let sessionInfo: SessionInfo = {
1818
session_id,
19-
platform: caps.platform,
20-
platform_name: caps.platformName.toUpperCase(),
21-
automation_name: caps.automationName,
22-
device_name: caps.deviceName,
23-
browser_name: caps.browserName,
24-
platform_version: caps.platformVersion,
19+
platform: caps.platform || "",
20+
platform_name: caps.platformName?.toUpperCase() || "",
21+
automation_name: caps.automationName || "",
22+
device_name: caps.deviceName || "",
23+
browser_name: caps.browserName || "",
24+
platform_version: caps.platformVersion || "",
2525
app: caps.app,
26-
udid: caps.platformName.toLowerCase() == "ios" ? caps.udid : caps.deviceUDID,
26+
udid: (caps.platformName?.toLowerCase() == "ios" ? caps.udid : caps.deviceUDID) || "",
2727
capabilities: {
2828
...caps,
2929
desired: rawCapabilities,

web/package-lock.json

Lines changed: 6 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "appium-dashboard-web",
3-
"version": "1.0.0-beta.13",
3+
"version": "2.0.0",
44
"private": true,
55
"dependencies": {
66
"@blueprintjs/core": "^3.51.3",

web/src/api/sessions.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ export default class SessionApi {
4040
return Api.delete(`/sessions/${sessionId}`);
4141
}
4242

43+
public static deleteAllSessions() {
44+
return Api.delete(`/sessions`);
45+
}
46+
4347
public static getSessionTextLogs(sessionId: string) {
4448
return Api.get(`/sessions/${sessionId}/logs/text`, {});
4549
}
Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,63 @@
11
import React from "react";
22
import styled from "styled-components";
3+
import Button from "./button";
34

45
const Container = styled.div`
5-
padding: 10px;
6+
display: flex;
7+
flex-direction: column;
8+
justify-content: center;
9+
align-items: center;
10+
height: 100%;
11+
gap: 10px;
12+
`;
13+
14+
const VideoContainer = styled.div`
15+
height: 90%;
16+
width: 100%;
617
`;
718

819
const StyledVideo = styled.video`
9-
position: absolute;
10-
top: 0;
11-
left: 0;
1220
width: 100%;
13-
height: 100%;
21+
height: auto;
22+
max-height: 100%;
23+
`;
24+
25+
const DownloadButton = styled.a`
26+
width: 50%;
27+
color: #fff;
28+
background: #6fc76b;
29+
padding: 5px;
30+
margin-right: 10px;
31+
margin-left: 10px;
32+
border-radius: 10px;
33+
text-align: center;
34+
font-size: 14px;
1435
`;
1536

1637
type PropsType = {
38+
session_id: string;
1739
url: string;
40+
downloadUrl: string;
1841
height?: number;
1942
width?: number;
2043
};
2144

2245
export default function VideoPlayer(props: PropsType) {
23-
const { url, width } = props;
46+
const { url, width, downloadUrl, session_id } = props;
2447
return (
2548
<Container>
26-
<StyledVideo
27-
className="react-player"
28-
src={url}
29-
width={width || "100%"}
30-
controls={true}
31-
/>
49+
<VideoContainer>
50+
<StyledVideo
51+
className="react-player"
52+
src={url}
53+
width={width || "100%"}
54+
controls={true}
55+
controlsList="nodownload"
56+
/>
57+
</VideoContainer>
58+
<DownloadButton href={downloadUrl} download={session_id}>
59+
Download Video
60+
</DownloadButton>
3261
</Container>
3362
);
3463
}

web/src/components/UI/organisms/app-header.tsx

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,47 @@
1-
import React from "react";
1+
import React, { useCallback } from "react";
2+
import { useDispatch, useSelector } from "react-redux";
23
import styled from "styled-components";
4+
import { getSessions } from "../../../store/selectors/entities/sessions-selector";
5+
import Button from "../atoms/button";
36
import ParallelLayout, { Column } from "../layouts/parallel-layout";
7+
import { deleteAllSession as deleteAllSessionAction } from "../../../store/actions/session-actions";
48
import HeaderLogo from "../molecules/header-logo";
59

610
const Container = styled.div`
711
border-bottom: 1px solid ${(props) => props.theme.colors.border};
812
`;
913

14+
const RightContainer = styled.div`
15+
display: flex;
16+
flex-direction: column;
17+
align-items: flex-end;
18+
justify-content: center;
19+
padding: 10px;
20+
`;
21+
1022
export default function AppHeader() {
23+
const sessions = useSelector(getSessions);
24+
const dispatch = useDispatch();
25+
26+
const deleteAllSession = useCallback(() => {
27+
dispatch(deleteAllSessionAction());
28+
}, []);
29+
1130
return (
1231
<Container>
1332
<ParallelLayout>
1433
<Column grid={3}>
1534
<HeaderLogo />
1635
</Column>
36+
{sessions.length > 0 ? (
37+
<Column grid={9}>
38+
<RightContainer>
39+
<Button onClick={deleteAllSession}>Delete all sessions</Button>
40+
</RightContainer>
41+
</Column>
42+
) : (
43+
<></>
44+
)}
1745
</ParallelLayout>
1846
</Container>
1947
);

0 commit comments

Comments
 (0)