Skip to content

Commit

Permalink
TRA-4275 keyboard shortcut for code push (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
RamiBerm authored Feb 20, 2022
1 parent cab7c80 commit e7dafda
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 65 deletions.
19 changes: 17 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"onCommand:up9.testAuth",
"onCommand:up9.startTunnel",
"onCommand:up9.stopTunnel",
"onCommand:up9.createTunneledLaunchConfig"
"onCommand:up9.createTunneledLaunchConfig",
"onCommand:up9.pushCode"
],
"main": "./out/extension.js",
"categories": [
Expand Down Expand Up @@ -54,6 +55,10 @@
{
"command": "up9.createTunneledLaunchConfig",
"title": "UP9: Create Tunneled Launch Config"
},
{
"command": "up9.pushCode",
"title": "UP9: Push Code"
}
],
"menus": {
Expand All @@ -76,9 +81,19 @@
{
"command": "up9.signOut",
"when": "false"
},
{
"command": "up9.pushCode",
"when": "false"
}
]
}
},
"keybindings": [
{
"command": "up9.pushCode",
"key": "ctrl+alt+p"
}
]
},
"scripts": {
"setup-browser": "cd src/webview && npm install && cd ../../",
Expand Down
4 changes: 4 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ export async function activate(context: vscode.ExtensionContext): Promise<vscode
});
const stopTunnelCommand = vscode.commands.registerCommand(stopTunnelCommandName, () => K8STunnel.getInstance().stop());
const createTunneledConfigCommand = vscode.commands.registerCommand(createTunneledConfigCommandName, () => runCreateTunneledLaunchConfig(K8STunnel.getInstance().getProxyAddress()));
const triggerPushTestCodeCommand = vscode.commands.registerCommand('up9.pushCode', () => {
UP9Panel?.currentPanel?.webviewCommunicator?.requestPushCodeFromPanel();
});


context.subscriptions.push(openTestBrowserCommand);
Expand All @@ -52,6 +55,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<vscode
context.subscriptions.push(startTunnelCommand);
context.subscriptions.push(stopTunnelCommand);
context.subscriptions.push(createTunneledConfigCommand);
context.subscriptions.push(triggerPushTestCodeCommand);

// return the context so its usable by tests
return context;
Expand Down
7 changes: 4 additions & 3 deletions src/panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ export class UP9Panel {
*/
public static currentPanel: UP9Panel | undefined;

public readonly webviewCommunicator: UP9WebviewCommunicator;

private readonly _panel: vscode.WebviewPanel;
private readonly _context: vscode.ExtensionContext;
private readonly _webviewCommunicator: UP9WebviewCommunicator;
private _disposables: vscode.Disposable[] = [];

public static createOrShow(context: vscode.ExtensionContext, up9Auth: UP9Auth) {
Expand All @@ -43,7 +44,7 @@ export class UP9Panel {
private constructor(panel: vscode.WebviewPanel, context: vscode.ExtensionContext, up9Auth: UP9Auth) {
this._panel = panel;
this._context = context;
this._webviewCommunicator = new UP9WebviewCommunicator(context, this._panel, up9Auth);
this.webviewCommunicator = new UP9WebviewCommunicator(context, this._panel, up9Auth);

// Set the webview's initial html content
this._panel.webview.html = this._getHtmlForWebview();
Expand All @@ -69,7 +70,7 @@ export class UP9Panel {
}

// Handle messages from the webview
this._webviewCommunicator.registerOnMessageListeners(this._disposables);
this.webviewCommunicator.registerOnMessageListeners(this._disposables);
}

public dispose() {
Expand Down
26 changes: 19 additions & 7 deletions src/providers/webviewCommunicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export class UP9WebviewCommunicator {
})();
break;
case MessageCommandType.PushText:
this.pushCodeToActiveEditor(message.code, message.testObject);
this.pushCodeToActiveEditor(message.testObject);
break;
case MessageCommandType.AuthSignOut:
(async () => {
Expand All @@ -81,6 +81,12 @@ export class UP9WebviewCommunicator {
await this.sendStoredDataToPanel();
}

public requestPushCodeFromPanel(): void {
this._panel.webview.postMessage({
command: MessageCommandType.PushText,
});
}

private notifyPanelOfAuthStateChange(authStatus: boolean): void {
try {
if (authStatus) {
Expand Down Expand Up @@ -182,7 +188,7 @@ export class UP9WebviewCommunicator {
});
}

private pushCodeToActiveEditor = (code: string, testObject: any) => {
private pushCodeToActiveEditor = (testObject: any) => {
let editor = vscode.window.activeTextEditor;
if (!editor) {
editor = vscode.window.visibleTextEditors?.[0];
Expand All @@ -197,18 +203,22 @@ export class UP9WebviewCommunicator {
if (currentEditorContents) {
//insert missing imports at the top
editor.edit(editBuilder => {
editBuilder.insert(new vscode.Position(this.getFirstLineNumberAfterImports(currentEditorContents), 0), this.getMissingImportsAndGlobalsInCode(currentEditorContents, testObject));
const globalsInsertLine = this.getFirstLineNumberAfterImports(currentEditorContents);
editBuilder.insert(new vscode.Position(globalsInsertLine, 0), this.getMissingImportsAndGlobalsInCode(currentEditorContents, testObject));

if (currentEditorContents.indexOf("class ") == -1) {
editBuilder.insert(editor.selection.active, `${microTestsClassDef}\n${code}`);
editBuilder.insert(editor.selection.active, `${microTestsClassDef}\n${testObject.code}`);
} else {
editBuilder.insert(editor.selection.active, `\n\n${code}`);
editBuilder.insert(editor.selection.active, `\n\n${testObject.code}`);
}
});
} else {
editor.insertSnippet(new vscode.SnippetString(`${microTestsHeader}\n${code}`));
editor.insertSnippet(new vscode.SnippetString(`${microTestsHeader}\n${testObject.code}`));
}
}


// gets first available line after imports and before class definition
private getFirstLineNumberAfterImports = (editorCode: string): number => {
const lines = editorCode.split("\n");
let firstLineNumber = 0;
Expand All @@ -219,14 +229,16 @@ export class UP9WebviewCommunicator {
if (line.startsWith("import ") || line.startsWith("from ")) {
firstLineNumber = parseInt(lineNumber);
}


// globals should always go before the class definition
if (line.startsWith("class")) {
break;
}
}
return firstLineNumber + 1;
}

// this function completes all necessary imports for our test code and adds a test url global if necessary
private getMissingImportsAndGlobalsInCode = (editorCode: string, testObject: any): string => {
const missingImports = [];

Expand Down
95 changes: 49 additions & 46 deletions src/webview/src/components/testCodeViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import { copyIcon, inputIcon } from "./svgs";
import {sendApiMessage, sendInfoToast, sendPushCodeToEditor } from "../providers/extensionConnectionProvider";
import { isHexColorDark, transformTest, getAssertionsCodeForSpan, getEndpointSchema, getTestCodeHeader } from "../utils";
import { ApiMessageType } from "../../../models/internal";
import { microTestsHeader } from "../../../consts";
import EndpointSchema from "./endpointSchema";
import { observer } from "mobx-react-lite";
import { testBrowserStore } from "../stores/testBrowserStore";
import { toJS } from "mobx";

enum TestCodeMode {
Test = "test",
Expand All @@ -27,15 +29,21 @@ export interface TestCodeViewerProps {
workspaceOAS: any;
}

const TestCodeViewer: React.FC<TestCodeViewerProps> = ({ workspace, endpoint, spans, workspaceOAS}) => {
const TestCodeViewer: React.FC<TestCodeViewerProps> = observer(({ workspace, endpoint, spans, workspaceOAS}) => {

const [isThemeDark, setIsThemeDark] = useState(null);
const [testsLoaded, setTestsLoaded] = useState(false);
const [endpointTest, setEndpointTest] = useState(null);
const [testCodeMode, setTestCodeMode] = useState(TestCodeMode.Test);

const editorBackgroundColor = getComputedStyle(document.documentElement).getPropertyValue('--vscode-editor-background');

useEffect(() => {
// clean up on dismount
return () => {
testBrowserStore.setSelectedEndpointTest(null);
}
}, []);

useEffect(() => {
setIsThemeDark(isHexColorDark(editorBackgroundColor))
}, [editorBackgroundColor]);
Expand All @@ -45,9 +53,25 @@ const TestCodeViewer: React.FC<TestCodeViewerProps> = ({ workspace, endpoint, sp
navigator.clipboard.writeText(text)
};

const endpointSpan = useMemo(() => {
if (!spans || !endpoint) {
return null;
}

return spans.find(span => span.uuid === endpoint.uuid);
}, [workspace, endpoint]);

const endpointSchema = useMemo(() => {
if (!endpoint || !workspaceOAS) {
return null;
}

return getEndpointSchema(endpoint, workspaceOAS);
}, [endpoint, workspaceOAS]);

useEffect(() => {
(async () => {
setEndpointTest(null);
testBrowserStore.setSelectedEndpointTest(null);
setTestsLoaded(false);
if (endpoint) {
try {
Expand All @@ -57,52 +81,31 @@ const TestCodeViewer: React.FC<TestCodeViewerProps> = ({ workspace, endpoint, sp
return;
}
const test = transformTest(tests.tests[0]);
console.log('test', test);
test.uuid = uuidv4(); //for react Key prop

setEndpointTest(test);
const testCode = test.code.replace('return resp', '');

//TODO: tidy this bit, this is too much logic for one hook
let generatedAssertions = '';
if (endpointSpan) {
try {
generatedAssertions = getAssertionsCodeForSpan(endpointSpan, ' ');
} catch (error) {
console.error("error generating assertions", error);
}
}

test.code = `${testCode}\n${generatedAssertions}`;

testBrowserStore.setSelectedEndpointTest(test);
} catch (error) {
console.log('error loading tests', error);
setTestsLoaded(false);
}

}
})()
}, [endpoint?.uuid]);

const endpointSpan = useMemo(() => {
if (!spans || !endpoint) {
return null;
}

return spans.find(span => span.uuid === endpoint.uuid);
}, [workspace, endpoint]);

const endpointSchema = useMemo(() => {
if (!endpoint || !workspaceOAS) {
return null;
}

return getEndpointSchema(endpoint, workspaceOAS);
}, [endpoint, workspaceOAS]);

const testCode = useMemo(() => {
if (!endpointTest) {
return null;
}
const testCode = endpointTest.code.replace('return resp', '');

let generatedAssertions = '';
if (endpointSpan) {
try {
generatedAssertions = getAssertionsCodeForSpan(endpointSpan, ' ');
} catch (error) {
console.error("error generating assertions", error);
}
}

return `${testCode}\n${generatedAssertions}`;
}, [endpointTest, endpointSchema, endpointSpan]);
}, [endpoint?.uuid, endpointSchema, endpointSpan]);

useEffect(() => {
// make sure ui doesnt reach a weird state where no schema is available and we hide the schema radio button
Expand All @@ -112,13 +115,13 @@ const TestCodeViewer: React.FC<TestCodeViewerProps> = ({ workspace, endpoint, sp
}, [endpointSchema, testCodeMode]);


if (testsLoaded && !endpointTest) {
if (testsLoaded && !testBrowserStore.selectedEndpointTest) {
return <p>No code found for this endpoint</p>;
} else if (!endpointTest) {
} else if (!testBrowserStore.selectedEndpointTest) {
return null;
}

const testCodeForDisplay = `${getTestCodeHeader(endpointTest)}\n${testCode}`;
const testCodeForDisplay = `${getTestCodeHeader(testBrowserStore.selectedEndpointTest)}\n${testBrowserStore.selectedEndpointTest.code}`;

return <div className="tests-list-container">
<Form.Group className="check-box-container">
Expand All @@ -133,7 +136,7 @@ const TestCodeViewer: React.FC<TestCodeViewerProps> = ({ workspace, endpoint, sp
<Container>
<Row>
<Col xs="2" md="2" lg="2" style={{"padding": "0"}}>
<span className="clickable" style={{marginRight: "10px"}} onClick={_ => sendPushCodeToEditor(testCode, endpointTest)}>{inputIcon}</span>
<span className="clickable" style={{marginRight: "10px"}} onClick={_ => sendPushCodeToEditor(toJS(testBrowserStore.selectedEndpointTest))}>{inputIcon}</span>
<span className="clickable" onClick={_ => copyToClipboard(testCodeForDisplay)}>{copyIcon}</span>
</Col>
<Col xs="10" md="10" lg="10" style={{"paddingLeft": "5px"}}></Col>
Expand All @@ -150,6 +153,6 @@ const TestCodeViewer: React.FC<TestCodeViewerProps> = ({ workspace, endpoint, sp
</Card>
</Container>
</div>
};
});

export default TestCodeViewer;
16 changes: 9 additions & 7 deletions src/webview/src/providers/extensionConnectionProvider.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { v4 as uuidv4 } from 'uuid';
import {up9AuthStore} from "../stores/up9AuthStore";
import { WebViewApiMessage, MessageCommandType, ApiMessageType } from "../../../models/internal";
import { testBrowserStore } from '../stores/testBrowserStore';
import { toJS } from 'mobx';

let isDebug = false;

Expand Down Expand Up @@ -720,10 +722,9 @@ export const setExtensionDefaultWorkspace = (workspaceId: string) => {
});
}

export const sendPushCodeToEditor = (code: string, testObject: any) => {
export const sendPushCodeToEditor = (testObject: any) => {
vsCodeApi.postMessage({
command: MessageCommandType.PushText,
code,
testObject
});
};
Expand All @@ -736,22 +737,18 @@ export const signOut = () => {

//handle messages incoming from the extension
window.addEventListener('message', event => {
console.log('received message', event.data);
const message = event.data;
switch (message.command) {
case MessageCommandType.AuthError:
console.log('received authError', message);
up9AuthStore.setAuthError(message.authError?.message ?? "unknown error occured");
up9AuthStore.setIsAuthConfigured(false);
break;
case MessageCommandType.AuthSuccess:
console.log('received authResponse', message);
up9AuthStore.setAuthError(null);
up9AuthStore.setIsAuthConfigured(true);
up9AuthStore.setUsername(message.username);
break;
case MessageCommandType.ApiResponse:
console.debug('received apiResponse', message);
const requestMessage = openApiMessages[message.data.apiMessageId];
if (!requestMessage) {
console.error("received message from extension with no local message object", message);
Expand All @@ -764,12 +761,17 @@ window.addEventListener('message', event => {
}
break;
case MessageCommandType.AuthSignOut:
console.log('received authSignOut', message);
up9AuthStore.setAuthError(null);
up9AuthStore.setIsAuthConfigured(false);
break;
case MessageCommandType.StoredData:
up9AuthStore.setDefaultWorkspace(message.defaultWorkspace);
up9AuthStore.setUP9Env(message.env);
break;
case MessageCommandType.PushText:
if (testBrowserStore.selectedEndpointTest != null) {
sendPushCodeToEditor(toJS(testBrowserStore.selectedEndpointTest));
}
break;
}
});
Loading

0 comments on commit e7dafda

Please sign in to comment.