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

Commit

Permalink
Add video download and bulk session delete functionalities (#97)
Browse files Browse the repository at this point in the history
  • Loading branch information
sudharsan-selvaraj authored Dec 14, 2022
1 parent 107c733 commit 566c289
Show file tree
Hide file tree
Showing 16 changed files with 16,594 additions and 5,427 deletions.
12,340 changes: 11,077 additions & 1,263 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "appium-dashboard",
"version": "v1.0.0-beta.14",
"version": "v2.0.0",
"description": "Appium plugin to view session execution details via detailed dashboard",
"main": "lib/index.js",
"scripts": {
Expand Down Expand Up @@ -74,5 +74,8 @@
"@types/teen_process": "^1.16.0",
"@types/uuid": "^8.3.1",
"typescript": "^4.4.4"
},
"peerDependencies": {
"appium": "^2.0.0-beta.46"
}
}
49 changes: 49 additions & 0 deletions src/app/controllers/session-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ export class SessionController extends BaseController {
router.get("/", this.getSessions.bind(this));
router.get("/:sessionId", this.getSessionBySessionId.bind(this));
router.delete("/:sessionId", (req, res, next) => this.deleteSession(req, res, next, config));
router.delete("/", (req, res, next) => this.deleteAllSession(req, res, next, config));
router.get("/:sessionId/log/:logId/screen-shot", this.getScreenShotForLog.bind(this));
router.get("/:sessionId/video", this.getVideoForSession.bind(this));
router.get("/:sessionId/video/download", this.downloadVideoForSession.bind(this));
router.get("/:sessionId/logs/text", this.getTextLogs.bind(this));
router.get("/:sessionId/logs/device", this.getDeviceLogs.bind(this));
router.get("/:sessionId/logs/debug", this.getDebugLogs.bind(this));
Expand Down Expand Up @@ -51,6 +53,37 @@ export class SessionController extends BaseController {
);
}

public async deleteAllSession(request: Request, response: Response, next: NextFunction, config: any) {
let sessions = await Session.findAll({
where: {
session_status: {
[Op.notIn]: ["RUNNING"],
},
},
});

await Session.destroy({
where: {
session_status: {
[Op.notIn]: ["RUNNING"],
},
},
});

for (var session of sessions) {
try {
if (session.video_path) {
fs.unlinkSync(session.video_path);
}
fs.rmdirSync(path.join(config.screenshotSavePath, session.session_id), { recursive: true });
} catch (err) {}
}

this.sendSuccessResponse(response, {
success: true,
});
}

public async deleteSession(request: Request, response: Response, next: NextFunction, config: any) {
let sessionId: string = request.params.sessionId;
let session = await Session.findOne({
Expand All @@ -73,6 +106,22 @@ export class SessionController extends BaseController {
}
}

public async downloadVideoForSession(request: Request, response: Response, next: NextFunction) {
let sessionId: string = request.params.sessionId;
let session = await Session.findOne({
where: {
session_id: sessionId,
},
});
const videoPath = session?.video_path;

if (session && videoPath) {
return response.download(videoPath);
} else {
this.sendFailureResponse(response, "Video not available");
}
}

public async getVideoForSession(request: Request, response: Response, next: NextFunction) {
let sessionId: string = request.params.sessionId;
const range = request.headers.range;
Expand Down
84 changes: 46 additions & 38 deletions src/plugin/session-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,50 +210,58 @@ class SessionManager {
}

private async sessionStarted(command: AppiumCommand) {
sessionDebugMap.createNewSession(this.sessionInfo.session_id);
try {
sessionDebugMap.createNewSession(this.sessionInfo.session_id);

this.driverScriptExecutor = new DriverScriptExecutor(this.sessionInfo, command.driver);
this.driverScriptExecutor = new DriverScriptExecutor(this.sessionInfo, command.driver);

/* Check if the current session supports network profiling */
if (isHttpLogsSuppoted(this.sessionInfo)) {
pluginLogger.info("Creating network profiler");
this.httpLogger = getHttpLogger({
sessionInfo: this.sessionInfo,
adb: this.adb,
driver: command.driver,
});
}
/* Check if the current session supports network profiling */
if (isHttpLogsSuppoted(this.sessionInfo)) {
pluginLogger.info("Creating network profiler");
this.httpLogger = getHttpLogger({
sessionInfo: this.sessionInfo,
adb: this.adb,
driver: command.driver,
});
}

let { desired } = this.sessionInfo.capabilities;
let buildName = desired["dashboard:build"];
let projectName = desired["dashboard:project"];
let name = desired["dashboard:name"];
let build, project;
let { desired } = this.sessionInfo.capabilities;
let buildName = desired["dashboard:build"];
let projectName = desired["dashboard:project"];
let name = desired["dashboard:name"];
let build, project;

let { is_profiling_available, device_info } = await this.startAppProfiling();
await this.startHttpLogsCapture();
let { is_profiling_available, device_info } = await this.startAppProfiling();
await this.startHttpLogsCapture();

if (projectName) {
project = await getOrCreateNewProject({ projectName });
}
if (buildName) {
build = await getOrCreateNewBuild({ buildName, projectId: project?.id });
}
if (projectName) {
project = await getOrCreateNewProject({ projectName });
}
if (buildName) {
build = await getOrCreateNewBuild({ buildName, projectId: project?.id });
}

await this.initializeScreenShotFolder();
await this.startScreenRecording(command.driver);
await Session.create({
...this.sessionInfo,
start_time: new Date(),
build_id: build?.build_id,
project_id: project?.id || null,
device_info,
is_profiling_available,
name: name || null,
live_stream_port: await getMjpegServerPort(command.driver, this.sessionInfo.session_id),
} as any);

await this.saveCommandLog(command, null);
await this.initializeScreenShotFolder();
await this.startScreenRecording(command.driver);
await Session.create({
...this.sessionInfo,
start_time: new Date(),
build_id: build?.build_id,
project_id: project?.id || null,
device_info,
is_profiling_available,
name: name || null,
live_stream_port: await getMjpegServerPort(command.driver, this.sessionInfo.session_id),
} as any);

await this.saveCommandLog(command, null);
} catch (err) {
logger.error(
`Error saving new session info in database for session ${
this.sessionInfo.session_id
}. response: ${JSON.stringify(err)}`
);
}
}

public async sessionTerminated(options: { sessionTimedOut: boolean } = { sessionTimedOut: false }) {
Expand Down
14 changes: 7 additions & 7 deletions src/plugin/utils/plugin-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ function getSessionDetails(rawCapabilities: any, sessionResponse: any): any {

let sessionInfo: SessionInfo = {
session_id,
platform: caps.platform,
platform_name: caps.platformName.toUpperCase(),
automation_name: caps.automationName,
device_name: caps.deviceName,
browser_name: caps.browserName,
platform_version: caps.platformVersion,
platform: caps.platform || "",
platform_name: caps.platformName?.toUpperCase() || "",
automation_name: caps.automationName || "",
device_name: caps.deviceName || "",
browser_name: caps.browserName || "",
platform_version: caps.platformVersion || "",
app: caps.app,
udid: caps.platformName.toLowerCase() == "ios" ? caps.udid : caps.deviceUDID,
udid: (caps.platformName?.toLowerCase() == "ios" ? caps.udid : caps.deviceUDID) || "",
capabilities: {
...caps,
desired: rawCapabilities,
Expand Down
17 changes: 6 additions & 11 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion web/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "appium-dashboard-web",
"version": "1.0.0-beta.13",
"version": "2.0.0",
"private": true,
"dependencies": {
"@blueprintjs/core": "^3.51.3",
Expand Down
4 changes: 4 additions & 0 deletions web/src/api/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ export default class SessionApi {
return Api.delete(`/sessions/${sessionId}`);
}

public static deleteAllSessions() {
return Api.delete(`/sessions`);
}

public static getSessionTextLogs(sessionId: string) {
return Api.get(`/sessions/${sessionId}/logs/text`, {});
}
Expand Down
53 changes: 41 additions & 12 deletions web/src/components/UI/atoms/video-player.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,63 @@
import React from "react";
import styled from "styled-components";
import Button from "./button";

const Container = styled.div`
padding: 10px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100%;
gap: 10px;
`;

const VideoContainer = styled.div`
height: 90%;
width: 100%;
`;

const StyledVideo = styled.video`
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
height: auto;
max-height: 100%;
`;

const DownloadButton = styled.a`
width: 50%;
color: #fff;
background: #6fc76b;
padding: 5px;
margin-right: 10px;
margin-left: 10px;
border-radius: 10px;
text-align: center;
font-size: 14px;
`;

type PropsType = {
session_id: string;
url: string;
downloadUrl: string;
height?: number;
width?: number;
};

export default function VideoPlayer(props: PropsType) {
const { url, width } = props;
const { url, width, downloadUrl, session_id } = props;
return (
<Container>
<StyledVideo
className="react-player"
src={url}
width={width || "100%"}
controls={true}
/>
<VideoContainer>
<StyledVideo
className="react-player"
src={url}
width={width || "100%"}
controls={true}
controlsList="nodownload"
/>
</VideoContainer>
<DownloadButton href={downloadUrl} download={session_id}>
Download Video
</DownloadButton>
</Container>
);
}
30 changes: 29 additions & 1 deletion web/src/components/UI/organisms/app-header.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,47 @@
import React from "react";
import React, { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import styled from "styled-components";
import { getSessions } from "../../../store/selectors/entities/sessions-selector";
import Button from "../atoms/button";
import ParallelLayout, { Column } from "../layouts/parallel-layout";
import { deleteAllSession as deleteAllSessionAction } from "../../../store/actions/session-actions";
import HeaderLogo from "../molecules/header-logo";

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

const RightContainer = styled.div`
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: center;
padding: 10px;
`;

export default function AppHeader() {
const sessions = useSelector(getSessions);
const dispatch = useDispatch();

const deleteAllSession = useCallback(() => {
dispatch(deleteAllSessionAction());
}, []);

return (
<Container>
<ParallelLayout>
<Column grid={3}>
<HeaderLogo />
</Column>
{sessions.length > 0 ? (
<Column grid={9}>
<RightContainer>
<Button onClick={deleteAllSession}>Delete all sessions</Button>
</RightContainer>
</Column>
) : (
<></>
)}
</ParallelLayout>
</Container>
);
Expand Down
Loading

0 comments on commit 566c289

Please sign in to comment.