Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { OperatorMetadataService } from "src/app/workspace/service/operator-meta
import { StubOperatorMetadataService } from "src/app/workspace/service/operator-metadata/stub-operator-metadata.service";

import { ContextMenuComponent } from "./context-menu.component";
import { HttpClientModule } from "@angular/common/http";

describe("ContextMenuComponent", () => {
let component: ContextMenuComponent;
Expand All @@ -12,6 +13,7 @@ describe("ContextMenuComponent", () => {
await TestBed.configureTestingModule({
declarations: [ContextMenuComponent],
providers: [{ provide: OperatorMetadataService, useClass: StubOperatorMetadataService }],
imports: [HttpClientModule],
}).compileComponents();
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable } from "@angular/core";
import { Injectable, Inject } from "@angular/core";
import { from, Observable, Subject } from "rxjs";
import { WorkflowActionService } from "../workflow-graph/model/workflow-action.service";
import { WorkflowGraphReadonly } from "../workflow-graph/model/workflow-graph";
Expand Down Expand Up @@ -27,6 +27,8 @@ import { WorkflowStatusService } from "../workflow-status/workflow-status.servic
import { isDefined } from "../../../common/util/predicate";
import { intersection } from "../../../common/util/set";
import { Workflow, WorkflowContent, WorkflowSettings } from "../../../common/type/workflow";
import { GmailService } from "src/app/common/service/gmail/gmail.service";
import { DOCUMENT } from "@angular/common";

// TODO: change this declaration
export const FORM_DEBOUNCE_TIME_MS = 150;
Expand Down Expand Up @@ -74,7 +76,9 @@ export class ExecuteWorkflowService {
private workflowActionService: WorkflowActionService,
private workflowWebsocketService: WorkflowWebsocketService,
private workflowStatusService: WorkflowStatusService,
private notificationService: NotificationService
private notificationService: NotificationService,
private gmailService: GmailService,
@Inject(DOCUMENT) private document: Document
) {
workflowWebsocketService.websocketEvent().subscribe(event => {
switch (event.type) {
Expand Down Expand Up @@ -298,6 +302,15 @@ export class ExecuteWorkflowService {
return;
}
this.updateWorkflowActionLock(stateInfo);
const isTransitionFromRunningToNonRunning =
this.currentState.state === ExecutionState.Running &&
[ExecutionState.Completed, ExecutionState.Failed, ExecutionState.Killed, ExecutionState.Paused].includes(
stateInfo.state
);

if (isTransitionFromRunningToNonRunning) {
this.sendWorkflowStatusEmail(stateInfo);
}
const previousState = this.currentState;
// update current state
this.currentState = stateInfo;
Expand Down Expand Up @@ -332,6 +345,51 @@ export class ExecuteWorkflowService {
}
}

/**
* Sends an email notification about the change in workflow state.
* This method constructs the email content with details such as the workflow ID, name,
* new state, and a timestamp, then sends it to the user's email address.
* The email is sent only if the current user is defined.
*
* @param stateInfo - The new execution state information containing the updated state of the workflow.
*/
private sendWorkflowStatusEmail(stateInfo: ExecutionStateInfo): void {
const workflow = this.workflowActionService.getWorkflow();
const timestamp =
new Date().toLocaleString("en-US", {
timeZone: "UTC",
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "numeric",
second: "numeric",
hour12: true,
}) + " (UTC)";

const baseUrl = this.document.location.origin;
const dashboardUrl = `${baseUrl}/dashboard/workspace/${workflow.wid}`;

const subject = `Workflow ${workflow.name} (${workflow.wid}) Status: ${stateInfo.state}`;
const content = `
Hello,

The workflow with the following details has changed its state:

- Workflow ID: ${workflow.wid}
- Workflow Name: ${workflow.name}
- State: ${stateInfo.state}
- Timestamp: ${timestamp}

You can view more details by visiting: ${dashboardUrl}

Regards,
Texera Team
`;

this.gmailService.sendEmail(subject, content);
}

/**
* Transform a workflowGraph object to the HTTP request body according to the backend API.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { OperatorMetadataService } from "../operator-metadata/operator-metadata.
import { StubOperatorMetadataService } from "../operator-metadata/stub-operator-metadata.service";

import { OperatorMenuService } from "./operator-menu.service";
import { HttpClientModule } from "@angular/common/http";

describe("OperatorMenuService", () => {
let service: OperatorMenuService;

beforeEach(() => {
TestBed.configureTestingModule({
providers: [{ provide: OperatorMetadataService, useClass: StubOperatorMetadataService }],
imports: [HttpClientModule],
});
service = TestBed.inject(OperatorMenuService);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { OperatorMetadataService } from "../operator-metadata/operator-metadata.
import { StubOperatorMetadataService } from "../operator-metadata/stub-operator-metadata.service";

import { OperatorReuseCacheStatusService } from "./operator-reuse-cache-status.service";
import { HttpClientModule } from "@angular/common/http";

describe("OperatorCacheStatusService", () => {
let service: OperatorReuseCacheStatusService;
Expand All @@ -15,6 +16,7 @@ describe("OperatorCacheStatusService", () => {
useClass: StubOperatorMetadataService,
},
],
imports: [HttpClientModule],
});
service = TestBed.inject(OperatorReuseCacheStatusService);
});
Expand Down