Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
8ccc5f9
initial wip
Yicong-Huang Oct 17, 2024
5652fb8
simplify
Yicong-Huang Oct 18, 2024
8809c3c
fix format
Yicong-Huang Oct 18, 2024
e855cb5
add conditional breakpoint
Yicong-Huang Oct 19, 2024
8e7d2a5
fix format
Yicong-Huang Oct 19, 2024
185edbe
separate code-debugger component
Yicong-Huang Oct 19, 2024
2cb64cc
single source of debug state
Yicong-Huang Oct 19, 2024
a12fc04
simplify
Yicong-Huang Oct 19, 2024
76fdce2
remove queue
Yicong-Huang Oct 19, 2024
9f25a96
add rerendering existing breakpoints logic
Yicong-Huang Oct 20, 2024
1a33a94
fix format
Yicong-Huang Oct 20, 2024
2135200
fix
Yicong-Huang Oct 20, 2024
8b2d586
simplify
Yicong-Huang Oct 20, 2024
e044c79
clean up
Yicong-Huang Oct 20, 2024
3281237
clean up
Yicong-Huang Oct 20, 2024
688cf4f
remove queue
Yicong-Huang Oct 20, 2024
db000bf
move package
Yicong-Huang Oct 20, 2024
5fc9685
revert some changes
Yicong-Huang Oct 20, 2024
5693aa1
fix
Yicong-Huang Oct 20, 2024
2046749
clean up
Yicong-Huang Oct 20, 2024
11c6f79
clean up
Yicong-Huang Oct 20, 2024
eadd79b
get rid of breakpoint manager
Yicong-Huang Oct 20, 2024
ef67e20
Merge branch 'master' into yicong-breakpoint-ui
Yicong-Huang Oct 22, 2024
a643216
fix merge
Yicong-Huang Oct 22, 2024
dce6aa0
add a test
Yicong-Huang Oct 22, 2024
83ceaed
fix format
Yicong-Huang Oct 22, 2024
bc4d14a
fix test
Yicong-Huang Oct 22, 2024
83b8484
fix test
Yicong-Huang Oct 22, 2024
a0b5d7d
Merge branch 'master' into yicong-breakpoint-ui
aglinxinyuan Oct 22, 2024
850f4c3
Merge branch 'master' into yicong-breakpoint-ui
Yicong-Huang Oct 24, 2024
3e7e819
Merge branch 'master' into yicong-breakpoint-ui
Yicong-Huang Oct 24, 2024
783ae8f
fix comments
Yicong-Huang Oct 24, 2024
b7a90be
upgrade to 0.2.0
Yicong-Huang Oct 24, 2024
a49d223
disable during states other than running or paused
Yicong-Huang Oct 24, 2024
4606671
disable during states other than running or paused
Yicong-Huang Oct 25, 2024
e066883
add test
Yicong-Huang Oct 25, 2024
91013c2
fix
Yicong-Huang Oct 25, 2024
41bee93
Merge branch 'master' into yicong-breakpoint-ui
Yicong-Huang Oct 25, 2024
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 @@ -17,6 +17,8 @@ def __call__(self, context: Context, command: cmd, *args, **kwargs):
context.debug_manager.put_debug_command(translated_command)

# allow MainLoop to switch into DataProcessor.
context.pause_manager.resume(PauseType.USER_PAUSE)
context.pause_manager.resume(PauseType.EXCEPTION_PAUSE)
context.pause_manager.resume(PauseType.DEBUG_PAUSE)

@staticmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ def __call__(self, context: Context, command: cmd, *args, **kwargs):
[context.tuple_processing_manager.current_input_tuple],
context.tuple_processing_manager.current_input_tuple_iter,
)
context.pause_manager.resume(PauseType.USER_PAUSE)
context.pause_manager.resume(PauseType.EXCEPTION_PAUSE)
return None
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ def pause_input_channel(
raise NotImplementedError()

def resume(self, pause_type: PauseType, change_state=True) -> None:
logger.debug("resume by " + str(pause_type))
if pause_type in self._global_pauses:
self._global_pauses.remove(pause_type)
# del self._specific_input_pauses[pause_type]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ package edu.uci.ics.amber.engine.architecture.controller.promisehandlers

import edu.uci.ics.amber.engine.architecture.controller.ControllerAsyncRPCHandlerInitializer
import edu.uci.ics.amber.engine.architecture.controller.promisehandlers.ConsoleMessageHandler.ConsoleMessageTriggered
import edu.uci.ics.amber.engine.architecture.controller.promisehandlers.PauseHandler.PauseWorkflow
import edu.uci.ics.amber.engine.architecture.worker.controlcommands.ConsoleMessage
import edu.uci.ics.amber.engine.common.rpc.AsyncRPCServer.ControlCommand
import edu.uci.ics.amber.engine.common.virtualidentity.util.CONTROLLER

object ConsoleMessageHandler {
case class ConsoleMessageTriggered(consoleMessage: ConsoleMessage) extends ControlCommand[Unit]
Expand All @@ -15,11 +13,6 @@ trait ConsoleMessageHandler {
this: ControllerAsyncRPCHandlerInitializer =>
registerHandler[ConsoleMessageTriggered, Unit] { (msg, sender) =>
{
if (msg.consoleMessage.msgType.isError) {
// if its an error message, pause the workflow
execute(PauseWorkflow(), CONTROLLER)
}

// forward message to frontend
sendToClient(msg)
}
Expand Down
11 changes: 7 additions & 4 deletions core/gui/custom-webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ module.exports = {
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
include: [require("path").resolve(__dirname, "node_modules/monaco-editor")],
include: [
require("path").resolve(__dirname, "node_modules/monaco-editor"),
require("path").resolve(__dirname, "node_modules/monaco-breakpoints")
],
},
],
// this is required for loading .wasm (and other) files.
// For context, see https://stackoverflow.com/a/75252098 and https://github.com/angular/angular-cli/issues/24617
parser: {
javascript: {
url: true
}
}
url: true,
},
},
},
};
1 change: 1 addition & 0 deletions core/gui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"jszip": "3.10.1",
"lodash-es": "4.17.21",
"marked": "4.3.0",
"monaco-breakpoints": "0.2.0",
"monaco-editor": "npm:@codingame/monaco-vscode-editor-api@8.0.4",
"monaco-editor-wrapper": "5.5.3",
"monaco-languageclient": "8.8.3",
Expand Down
4 changes: 4 additions & 0 deletions core/gui/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ import { HubWorkflowResultComponent } from "./hub/component/workflow/result/hub-
import { HubWorkflowComponent } from "./hub/component/workflow/hub-workflow.component";
import { HubWorkflowSearchBarComponent } from "./hub/component/workflow/search-bar/hub-workflow-search-bar.component";
import { HubWorkflowDetailComponent } from "./hub/component/workflow/detail/hub-workflow-detail.component";
import { BreakpointConditionInputComponent } from "./workspace/component/code-editor-dialog/breakpoint-condition-input/breakpoint-condition-input.component";
import { CodeDebuggerComponent } from "./workspace/component/code-editor-dialog/code-debugger.component";

registerLocaleData(en);

Expand Down Expand Up @@ -226,6 +228,8 @@ registerLocaleData(en);
HubWorkflowDetailComponent,
HubWorkflowResultComponent,
GoogleLoginComponent,
BreakpointConditionInputComponent,
CodeDebuggerComponent,
],
imports: [
BrowserModule,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<div
*ngIf="isVisible"
class="condition-input-popup"
[ngStyle]="{ top: topPosition, left: leftPosition }">
<div class="tooltip-header">Condition on line {{ lineNum }}:</div>
<textarea
#conditionTextarea
[(ngModel)]="condition"
class="condition-textarea"
rows="2">
</textarea>
{{ conditionTextarea.focus() }}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
.condition-input-popup {
position: absolute;
background: #333;
color: #fff;
padding: 5px; /* Larger padding for better UX */
border-radius: 3px; /* Smoother rounded corners */
z-index: 1000;
pointer-events: auto;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
display: flex;
flex-direction: column;
align-items: flex-start;
width: 240px;
opacity: 1;
transition: opacity 1s ease-in-out;
}

.condition-input-popup .tooltip-header {
font-weight: bold;
margin-bottom: 5px;
white-space: nowrap;
flex-shrink: 0;
}

.condition-input-popup .condition-textarea {
width: 100%;
height: 50px;
resize: vertical;
background-color: transparent;
color: white;
border: 1px solid #ccc;
padding: 5px;
}

.condition-input-popup.fade-out {
opacity: 0;
pointer-events: none;
}

::ng-deep .cgmr.codicon.monaco-conditional-breakpoint {
width: 10px !important;
height: 10px !important;
border-radius: 100%;
background-color: #d47d78;
margin: 4px 0 0 8px;
cursor: pointer;
color: #000;
text-align: center;
font-size: 8px;
font-weight: bold;
}

::ng-deep .cgmr.codicon.monaco-conditional-breakpoint::before {
content: "?";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { BreakpointConditionInputComponent } from "./breakpoint-condition-input.component";
import { UdfDebugService } from "../../../service/operator-debug/udf-debug.service";
import { SimpleChanges } from "@angular/core";
import * as monaco from "monaco-editor";

describe("BreakpointConditionInputComponent", () => {
let component: BreakpointConditionInputComponent;
let fixture: ComponentFixture<BreakpointConditionInputComponent>;
let mockUdfDebugService: jasmine.SpyObj<UdfDebugService>;
let editorElement: HTMLElement;

beforeEach(async () => {
// Create a mock UdfDebugService
mockUdfDebugService = jasmine.createSpyObj("UdfDebugService", ["getCondition", "doUpdateBreakpointCondition"]);

await TestBed.configureTestingModule({
declarations: [BreakpointConditionInputComponent],
providers: [{ provide: UdfDebugService, useValue: mockUdfDebugService }],
}).compileComponents();

fixture = TestBed.createComponent(BreakpointConditionInputComponent);
component = fixture.componentInstance;

// Create and attach a <div> to host the Monaco editor
editorElement = document.createElement("div");
editorElement.style.width = "800px";
editorElement.style.height = "600px";
document.body.appendChild(editorElement); // Attach to the DOM

// Initialize the Monaco editor
component.monacoEditor = monaco.editor.create(editorElement, {
value: "function hello() {\n\tconsole.log(\"Hello, world!\");\n}",
language: "javascript",
});

// Set required inputs
component.operatorId = "test-operator";
component.lineNum = 1;

fixture.detectChanges(); // Trigger Angular's change detection
});

afterEach(() => {
// Clean up the editor and DOM element after each test
component.monacoEditor.dispose();
editorElement.remove();
component.closeEmitter.emit();
});

it("should create the component", () => {
expect(component).toBeTruthy();
});

it("should update the condition when lineNum changes", () => {
mockUdfDebugService.getCondition.and.returnValue("existing condition");

const changes: SimpleChanges = {
lineNum: {
currentValue: 2,
previousValue: 1,
firstChange: false,
isFirstChange: () => false,
},
};

component.ngOnChanges(changes);

expect(component.condition).toBe("existing condition");
});

it("should handle Enter key event and save the condition", () => {
const emitSpy = spyOn(component.closeEmitter, "emit");
const event = new KeyboardEvent("keydown", { key: "Enter" });

component.condition = " new condition ";
component.handleEvent(event);

expect(mockUdfDebugService.doUpdateBreakpointCondition).toHaveBeenCalledWith("test-operator", 1, "new condition");
expect(emitSpy).toHaveBeenCalled();
});

it("should not handle Enter key event if shift key is pressed", () => {
const emitSpy = spyOn(component.closeEmitter, "emit");
const event = new KeyboardEvent("keydown", { key: "Enter", shiftKey: true });

component.handleEvent(event);

expect(mockUdfDebugService.doUpdateBreakpointCondition).not.toHaveBeenCalled();
expect(emitSpy).not.toHaveBeenCalled();
});

it("should emit close event on focusout", () => {
const emitSpy = spyOn(component.closeEmitter, "emit");

component.handleEvent(); // Simulate focusout

expect(emitSpy).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import {
AfterViewChecked,
Component,
ElementRef,
EventEmitter,
HostListener,
Input,
OnChanges,
Output,
SimpleChanges,
ViewChild,
} from "@angular/core";
import { UdfDebugService } from "../../../service/operator-debug/udf-debug.service";
import { isDefined } from "../../../../common/util/predicate";
import { MonacoEditor } from "monaco-breakpoints/dist/types";

/**
* This component is a dialog that allows users to input a condition for a breakpoint.
*/
@Component({
selector: "texera-breakpoint-condition-input",
templateUrl: "./breakpoint-condition-input.component.html",
styleUrls: ["./breakpoint-condition-input.component.scss"],
})
export class BreakpointConditionInputComponent implements OnChanges {
constructor(private udfDebugService: UdfDebugService) {}

@Input() operatorId = "";
@Input() lineNum?: number;
@Input() monacoEditor!: MonacoEditor;
@Output() closeEmitter = new EventEmitter<void>();
public condition = "";
public topPosition: string = "0px";
public leftPosition: string = "0px";
ngOnChanges(changes: SimpleChanges): void {
if (!isDefined(changes["lineNum"]?.currentValue)) {
return;
}
// when the line number changes, update the condition
this.condition = this.udfDebugService.getCondition(this.operatorId, this.lineNum!) ?? "";

// update position
const layoutInfo = this.monacoEditor.getLayoutInfo();
const editorRect = this.monacoEditor.getDomNode()?.getBoundingClientRect();
const topValue =
(editorRect?.top || 0) +
this.monacoEditor.getBottomForLineNumber(this.lineNum!) -
this.monacoEditor.getScrollTop();
const leftValue = (editorRect?.left || 0) + (layoutInfo?.glyphMarginLeft || 0) - 160;
this.topPosition = `${topValue}px`;
this.leftPosition = `${leftValue}px`;
}

public left(): number {
if (!isDefined(this.monacoEditor)) {
return 0;
}

// Calculate the left position of the input popup based on the editor layout
const { glyphMarginLeft } = this.monacoEditor.getLayoutInfo()!;
const { left } = this.monacoEditor.getDomNode()!.getBoundingClientRect();
return left + glyphMarginLeft - this.monacoEditor.getScrollLeft() - 160;
}

public top(): number {
if (!(isDefined(this.monacoEditor) && isDefined(this.lineNum))) {
return 0;
}

// Calculate the top position of the input popup based on the editor layout
const topPixel = this.monacoEditor.getBottomForLineNumber(this.lineNum);
const editorRect = this.monacoEditor.getDomNode()?.getBoundingClientRect();
return (editorRect?.top || 0) + topPixel - this.monacoEditor.getScrollTop();
}

get isVisible(): boolean {
return isDefined(this.lineNum);
}

/**
* Update the condition and close the dialog when the user presses Enter or focus out.
* @param event the keyboard event, or undefined if the event is focus out.
*/
@HostListener("window:keydown", ["$event"])
@HostListener("focusout")
handleEvent(event?: KeyboardEvent): void {
if (!this.lineNum || (event && !(event.key === "Enter" && !event.shiftKey))) {
// perform no changes if no line number or the key is not Enter
return;
}

// prevent the default behavior of the Enter key
event?.preventDefault();

// save the updated condition
this.udfDebugService.doUpdateBreakpointCondition(this.operatorId, this.lineNum, this.condition.trim());

// close the dialog
this.closeEmitter.emit();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<texera-breakpoint-condition-input
[operatorId]="this.currentOperatorId"
[lineNum]="breakpointConditionLine"
[monacoEditor]="monacoEditor"
(closeEmitter)="closeBreakpointConditionInput()">
</texera-breakpoint-condition-input>
Loading