Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
d81c431
Fix: added time zone property for decoding the user time
aakashreddy-p Oct 7, 2025
d1b67f4
Fix: I have updated module function to handle empty testflows
aakashreddy-p Oct 7, 2025
1d834dc
Fix: I have updated module function to handle empty testflows
aakashreddy-p Oct 7, 2025
06f35b7
Fix: I have updated module function to handle empty testflows
aakashreddy-p Oct 7, 2025
dda25e9
Fix: I have updated module function to handle empty testflows
aakashreddy-p Oct 7, 2025
e3c66f4
Merge pull request #846 from aakashreddy-p/aakash/fix/testflow-empty-…
itsmdasifraza Oct 7, 2025
07c940e
Fix: I have resolved template issues
aakashreddy-p Oct 7, 2025
6bde57a
conflict resolved
aakashreddy-p Oct 7, 2025
678361c
fix: time updated according to the Utc
aakashreddy-p Oct 8, 2025
79f42e4
fix: time updated according to the Utc
aakashreddy-p Oct 8, 2025
9a50218
feat: add permission check to get a testflow
itsmdasifraza Oct 8, 2025
b9c634a
fix: time updated according to the Utc
aakashreddy-p Oct 8, 2025
d2c63a5
fix: time updated according to the Utc
aakashreddy-p Oct 8, 2025
2aa3ec5
fix: time updated according to the Utc
aakashreddy-p Oct 8, 2025
6b20415
Merge pull request #845 from aakashreddy-p/aakash/fix/time-formatting…
itsmdasifraza Oct 8, 2025
7525e10
Merge pull request #848 from itsmdasifraza/feat/add-permission-check-…
Astitva877 Oct 8, 2025
c406072
feat: add permission check to get a testflow
itsmdasifraza Oct 8, 2025
9a90c0c
Merge pull request #849 from itsmdasifraza/feat/add-permission-check-…
Astitva877 Oct 8, 2025
03e55aa
fix: cron expression on edit
itsmdasifraza Oct 8, 2025
3e40503
fix: cron expression on edit
itsmdasifraza Oct 8, 2025
0f8f274
fix: testflow cron job configurations
itsmdasifraza Oct 8, 2025
524e1d4
fix: testflow cron job configurations
itsmdasifraza Oct 8, 2025
20d8fe8
Merge pull request #850 from itsmdasifraza/feat/add-permission-check-…
Astitva877 Oct 9, 2025
fe16c1d
feat: add error handling for proxy fail
itsmdasifraza Oct 9, 2025
2af9291
feat: add error handling for proxy fail
itsmdasifraza Oct 9, 2025
97838cd
feat: add error handling for proxy fail
itsmdasifraza Oct 9, 2025
e70d150
Merge pull request #851 from itsmdasifraza/feat/add-permission-check-…
Astitva877 Oct 9, 2025
7ef80c8
fix: added margin for text name
aakashreddy-p Oct 9, 2025
6fcf284
Merge pull request #852 from aakashreddy-p/aakash/fix/email-template-…
itsmdasifraza Oct 9, 2025
16f0a27
feat: add hourly cron fix
itsmdasifraza Oct 9, 2025
409ca10
feat: updated hourly cron expression
itsmdasifraza Oct 10, 2025
f38693b
feat: updated hourly cron expression
itsmdasifraza Oct 10, 2025
e8702c3
feat: updated hourly cron expression
itsmdasifraza Oct 10, 2025
beb6eda
feat: updated hourly cron expression
itsmdasifraza Oct 10, 2025
5087fa7
Merge pull request #854 from itsmdasifraza/feat/add-permission-check-…
Astitva877 Oct 10, 2025
353fc7e
feat: updated hourly cron expression
itsmdasifraza Oct 10, 2025
b2b0cbc
Merge pull request #855 from itsmdasifraza/feat/add-permission-check-…
Astitva877 Oct 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/modules/common/enum/testflow.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ export enum RequestDataTypeEnum {
export interface EmailData {
userName: string;
scheduleName: string;
scheduleLastestRun: Date;
scheduleLastestRun: string;
scheduleRunResult: string;
scheduleRunPassedCount: number;
scheduleRunFailedCount: number;
scheduleRunTotalRequest: number;
scheduleRunPassPercentage: number;
scheduleRunPassPercentage: string;
scheduleTotalTime: string | number;
scheduleRunEnvName: string;
isSuccess: boolean;
Expand All @@ -61,6 +61,7 @@ export interface DailyConfig {

export interface HourlyConfig {
type: RunCycleEnum.HOURLY;
executeAt: Date;
intervalHours: number;
startTime?: {
hour: number;
Expand Down
26 changes: 18 additions & 8 deletions src/modules/views/testflowScheduleRunEmail.handlebars
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
{{else if isFailed}}
❌️ <span class="status" style=""> Failed</span>
{{else if isPartial}}
⚠️ <span class="status" style=""> Partially Failed</span><span>({{scheduleRunPassedCount}}/{{scheduleRunTotalRequest}} Requests Failed)</span>
⚠️ <span class="status" style=""> Partially Failed</span><span>({{scheduleRunFailedCount}}/{{scheduleRunTotalRequest}} Requests Failed)</span>
{{/if}}
</p>
</div>
Expand All @@ -126,7 +126,7 @@
<table style="margin-bottom: 20px;" class="email-text-style" width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td style="border-bottom:1px solid #b0b8c1" colspan="2">
<h1 style="margin-top:0; margin-bottom: -6px; font-size: 20px; font-weight: 600;">Run Summary</h1>
<h1 style="margin-top:0; font-size: 20px;font-weight: 600;margin-bottom: 15px;">Run Summary</h1>
</td>
</tr>
<tr>
Expand All @@ -143,7 +143,7 @@
</tr>
<tr>
<td style="border-bottom:1px solid #e0e0e0;">Pass Rate</td>
<td style="text-align:start; padding:12px 160px 12px 96px; border-bottom:1px solid #e0e0e0;">{{scheduleRunPassPercentage}}</td>
<td style="text-align:start; padding:12px 160px 12px 96px; border-bottom:1px solid #e0e0e0;">{{scheduleRunPassPercentage}}%</td>
</tr>
<tr>
<td style="border-bottom:1px solid #e0e0e0;">Total Duration</td>
Expand Down Expand Up @@ -178,11 +178,21 @@
<div>
<table cellspacing="0" cellpadding="0" border="0">
<tr>
<td style="border-radius: 6px; background-color: #316CF6; padding: 6px 12px; min-height: 36px; cursor:pointer;">
<a href="https://web.sparrowapp.dev/app/collections" class="email-text-style" style="display: inline-block; text-decoration: none;">
View Report
</a>
</td>
<td align="left" valign="middle" style="">
<div>
<div>
<table cellspacing="0" cellpadding="0" border="0">
<tr>
<td style="border-radius: 6px; background-color: #316CF6; padding: 6px 12px; min-height: 36px;">
<a href="https://web.sparrowapp.dev/app/collections" class="email-text-style" style="display: inline-block; color: #ffffff; text-decoration: none;">
View Report
</a>
</td>
</tr>
</table>
</div>
</div>
</td>
</tr>
</table>
</div>
Expand Down
19 changes: 11 additions & 8 deletions src/modules/workspace/controllers/testflow.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export class TestflowController {
* @description This will retrieve a specific Testflow using its ID,
* returning the Testflow object if found.
*/
@Get("testflow/:testflowId")
@Get(":workspaceId/testflow/:testflowId")
@ApiOperation({
summary: "Get Individual Testflow",
description: "This will get individual testflow of a workspace",
Expand All @@ -121,10 +121,13 @@ export class TestflowController {
})
@ApiResponse({ status: 400, description: "Fetch Testflow Request Failed" })
async getTestflow(
@Param("workspaceId") workspaceId: string,
@Param("testflowId") testflowId: string,
@Res() res: FastifyReply,
@Req() request: ExtendedFastifyRequest,
) {
const testflow = await this.testflowService.getTestflow(testflowId);
const user = request.user;
const testflow = await this.testflowService.getTestflow(workspaceId, testflowId, user._id);
const responseData = new ApiResponseService(
"Success",
HttpStatusCode.OK,
Expand Down Expand Up @@ -321,8 +324,8 @@ export class TestflowController {
const response = await this.testflowService.createTestflowSchedular(
createTestflowSchedularDto,
user,
);
const testflow = await this.testflowService.getTestflow(createTestflowSchedularDto.testflowId);
);
const testflow = await this.testflowService.getTestflow(createTestflowSchedularDto.workspaceId, createTestflowSchedularDto.testflowId, user._id);
const result = {
testflow,
schedule:response
Expand Down Expand Up @@ -359,7 +362,7 @@ export class TestflowController {
workspaceId,
user,
);
const testflow = await this.testflowService.getTestflow(testflowId);
const testflow = await this.testflowService.getTestflow(workspaceId, testflowId, user._id);
const responseData = new ApiResponseService(
"Success",
HttpStatusCode.OK,
Expand Down Expand Up @@ -390,7 +393,7 @@ export class TestflowController {
workspaceId,
user,
);
const testflow = await this.testflowService.getTestflow(testflowId);
const testflow = await this.testflowService.getTestflow(workspaceId, testflowId, user._id);
const responseData = new ApiResponseService(
"Success",
HttpStatusCode.OK,
Expand Down Expand Up @@ -421,7 +424,7 @@ export class TestflowController {
workspaceId,
user,
);
const testflow = await this.testflowService.getTestflow(testflowId);
const testflow = await this.testflowService.getTestflow(workspaceId, testflowId, user._id);
const responseData = new ApiResponseService(
"Success",
HttpStatusCode.OK,
Expand All @@ -448,7 +451,7 @@ export class TestflowController {
) {
const user = request.user;
await this.testflowService.deleteScheduleRunHistory(workspaceId, testflowId, scheduleId, runHistoryId, user);
const testflow = await this.testflowService.getTestflow(testflowId);
const testflow = await this.testflowService.getTestflow(workspaceId, testflowId, user._id);
const responseData = new ApiResponseService(
"Success",
HttpStatusCode.OK,
Expand Down
57 changes: 44 additions & 13 deletions src/modules/workspace/repositories/testflow.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ export class TestflowRepository {
schedularId: string,
runHistoryItem: TestFlowSchedularRunHistory,
): Promise<UpdateResult> {
const now = new Date();
const nowUtc = new Date().toISOString();
if (!testflowId || !schedularId) {
throw new Error("Both testflowId and schedularId are required");
}
Expand All @@ -215,8 +215,8 @@ export class TestflowRepository {
{
$inc: { "schedules.$[elem].executedCount": 1 },
$set: {
"schedules.$[elem].lastExecuted": now,
updatedAt: now,
"schedules.$[elem].lastExecuted": nowUtc,
updatedAt: nowUtc,
},
$push: {
"schedules.$[elem].schedularRunHistory": {
Expand All @@ -231,34 +231,65 @@ export class TestflowRepository {
);
}

/**
/**
* Edit a schedular execution (run history item) in a testflow's schedule.
* @param {string} testflowId - The testflow document ID.
* @param {string} schedularId - The schedule ID.
* @param {Partial<TestFlowSchedularRunHistory>} updatedRunHistory - The updated fields for the run history item.
* @returns {Promise<UpdateResult>} - The result of the update operation.
*/
async editSchedularExecution(
testflowId: string,
schedularId: string,
updatedRunHistory: Partial<TestFlowSchedularRunHistory>,
): Promise<UpdateResult> {
if (!testflowId || !schedularId || !updatedRunHistory.id) {
throw new Error("testflowId, schedularId, and runHistoryId are required");
}
// Build the update object for only the provided fields
const setObj: Record<string, any> = {};
for (const [key, value] of Object.entries(updatedRunHistory)) {
setObj[`schedules.$[elem].schedularRunHistory.$[run].${key}`] = value;
}
setObj["updatedAt"] = new Date();
return this.db.collection(Collections.TESTFLOW).updateOne(
{ _id: new ObjectId(testflowId) },
{ $set: setObj },
{
arrayFilters: [
{ "elem.id": schedularId },
{ "run.id": updatedRunHistory.id },
],
},
);
}

/**
* Edit a schedular execution (run history item) in a testflow's schedule.
* @param {string} testflowId - The testflow document ID.
* @param {string} schedularId - The schedule ID.
* @param {Partial<TestFlowSchedularRunHistory>} updatedRunHistory - The updated fields for the run history item.
* @param {Partial<TestFlowSchedular>} updatedSchedular - The updated fields for the schedule item.
* @returns {Promise<UpdateResult>} - The result of the update operation.
*/
async editSchedularExecution(
async editSchedular(
testflowId: string,
schedularId: string,
updatedRunHistory: Partial<TestFlowSchedularRunHistory>,
updatedSchedular: Partial<TestflowSchedular>,
): Promise<UpdateResult> {
if (!testflowId || !schedularId || !updatedRunHistory.id) {
throw new Error("testflowId, schedularId, and runHistoryId are required");
if (!testflowId || !schedularId) {
throw new Error("testflowId and schedularId are required");
}
// Build the update object for only the provided fields
const setObj: Record<string, any> = {};
for (const [key, value] of Object.entries(updatedRunHistory)) {
setObj[`schedules.$[elem].schedularRunHistory.$[run].${key}`] = value;
for (const [key, value] of Object.entries(updatedSchedular)) {
setObj[`schedules.$[elem].${key}`] = value;
}
setObj["updatedAt"] = new Date();
return this.db.collection(Collections.TESTFLOW).updateOne(
{ _id: new ObjectId(testflowId) },
{ $set: setObj },
{
arrayFilters: [
{ "elem.id": schedularId },
{ "run.id": updatedRunHistory.id },
],
},
);
Expand Down
67 changes: 38 additions & 29 deletions src/modules/workspace/services/testflow-run.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,33 +73,35 @@ export class TestflowRunService {
testflowId:string,
user?: DecodedUserObject,
): Promise<any> => {
// Fetch testflow and environment data
const testflowDetails = await this.testflowRepository.get(testflowId);
const workspaceID = await this.workspaceReposistory.get(workspaceId);
const globalEnvironment = workspaceID.environments[0];
const globalEnvDetails = await this.environmentReposistory.get(
globalEnvironment.id.toString(),
);
let environmentData;
if (environmentId) {
environmentData =
await this.environmentReposistory.get(environmentId);
}
const activeVariables = this.combineEnvironmentData(
globalEnvDetails?.variable || [],
environmentData?.variable || [],
);
// Build proxy URL
const sparrowProxy = this.configService.get<string>("sparrowProxy.baseUrl");
const proxyUrl = `${sparrowProxy}/proxy/testflow/execute`;
// Prepare request body for proxy API
const body = {
nodes: testflowDetails.nodes || [],
variables: activeVariables || [],
edges: testflowDetails.edges,
userId: user?._id || new ObjectId("000000000000000000000000"),
};
try {
// Fetch testflow and environment data
const testflowDetails = await this.testflowRepository.get(testflowId);
const workspaceID = await this.workspaceReposistory.get(workspaceId);
const globalEnvironment = workspaceID.environments[0];
const globalEnvDetails = await this.environmentReposistory.get(
globalEnvironment.id.toString(),
);
let environmentData;
if (environmentId) {
try{
environmentData =
await this.environmentReposistory.get(environmentId);
}catch(err){}
}
const activeVariables = this.combineEnvironmentData(
globalEnvDetails?.variable || [],
environmentData?.variable || [],
);
// Build proxy URL
const sparrowProxy = this.configService.get<string>("sparrowProxy.baseUrl");
const proxyUrl = `${sparrowProxy}/proxy/testflow/execute`;
// Prepare request body for proxy API
const body = {
nodes: testflowDetails.nodes || [],
variables: activeVariables || [],
edges: testflowDetails.edges,
userId: user?._id || new ObjectId("000000000000000000000000"),
};
const response = await axios.post(proxyUrl, body, {
headers: {
"Content-Type": "application/json",
Expand All @@ -111,11 +113,18 @@ export class TestflowRunService {
nodes:testflowDetails.nodes,
edges:testflowDetails.edges,
}
// Return only history or any relevant part
return finalResult;
} catch (error: any) {
console.error("Testflow proxy execution failed:", error.message || error);
throw new Error(error?.message || "Testflow execution failed.");
return {
result:{
history: {
status: "error"
}
},
environmentName: "",
nodes: [],
edges: [],
}
}
};
}
Loading
Loading